Dobre praktyki podczas tworzenia aplikacji kilka pytań

0

Jako że aplikacja którą współtworzę w pracy jest spora, i ciągle się rozrasta, a mi brak jest doświadczenia zacząłem widzieć problemy których wcześniej pisząc małe aplikacje w domu nie widziałem. W związku z tym mam kilka pytań.

  1. Jedną z rzeczy która mnie najbardziej irytuje jest pobieranie tych samych danych z bazy w wielu miejscach w kodzie. Powiedzmy mam taką sytuacje że mam trzy widoki wyświetlane z trzech różnych metod akcji. W każdym z tych widoków wyświetlam jakieś dane które są po części wspólne dla tych wszystkich widoków.
    Co robić w takiej sytuacji, pobrać wszystkie dane z bazy w pierwszej metodzie akcji a potem zapisać je w sesji przeglądarki ?
    Czy jest coś złego z korzystania sesji w przeglądarce/ na serwerze ?
    Inna sytuacja jest taka że w każdej metodzie akcji pobieram coś z bazy. Często są to te same dane co w poprzedniej metodzie. Czy takie podejście jest okej ? Ja jakoś jestem sceptycznie nastawiony do przysyłania 7 parametrów z widoku do metody z metody do widoku z widoku do metody i z metody do widoku.. Zauważyłem że w aplikacji WPF trzymają jakoś wypełnione obiekty, do których w każdej chwili mogą się odezwać i pobrać dane.

  2. To pytanie jest bardzo ogólne. Chodzi o to jak pisać takie duże aplikacje ASP.NET MVC, bo u nas większość robiona jest w kontrolerach -> strzał do serwisu -> baza -> odpowiedź. W efekcie czego robi się rozpierdol. Szczególnie jak metoda akcji ma się zachowywać w różnoraki sposób w zależności od sytuacji. Staram się pisać dodatkowe metody, żeby w metodzie akcji nie było 300 linijek kodu, ale na tym kończę. Bynajmniej ja mam takie odczucie że coś tu jest nie tak. Wiem że to bardzo szeroki temat itd, ale bardzo chciałbym poznać jakieś małe rzeczy jakieś Wasze zwyczaje np, jakie tworzycie sobie dodatkowe foldery/ klasy do czego służą. Może jakim wzorcom powinienem się zainteresować.

  3. Jeżeli metoda akcji zwraca mi model widoku w którym mam kilka modeli, to do zwracanego modelu do nazwy dodaję viewmodel natomiast do składowych klas tylko model czy takie podejście jest okej ?

  4. Powiedzmy że tworzę sobie metodę która mi zwraca dwie, trzy albo n rzeczy. I teraz mam pytanie, czy mam tworzyć nową klasę i zwrócić jej obiekt z tymi danymi? Czy tak się powinno robić ? Czasami myślałem żeby zwrócić jakiegoś tupla albo jakąś listę jak chciałem zwrócić dwie rzeczy z metody ale nie wydawało mi się to zbyt fajne.

  5. Jak sobie radzić z ifami, czy to jest zło ? Bo spotkałem się ze stwierdzeniem że if w ifie to max a co jak mam tego więcej ?

  6. Jeżeli mam metodę która mi zwraca jakiś string, na podstawie którego rozpoznaje co powinienem dalej zrobić, to czy nie lepiej zwrócić go jako enum ? Wiadomo to łańcuch znaków łatwo o literówkę, czy może zwracać cyfry (1, 2, 3) i np. jeżeli zwróciło 1 zrób to, 2 to to itd.

@AreQrm @somekind bardzo proszę Was o jakiś komentarz.

0

może podpowiem, abyś zainteresował się Windows Workflow Fundation silnikiem różnych tworów biznesowo-kodowych co pozwala jednym i drugim jakoś znajdować wspólny temat...

4

@RideorDie - chciałbym pomóc, ale wiele Twoich pytań jest bardzo ogólnych i potrzebuję doprecyzowania.

RideorDie napisał(a):
  1. Jedną z rzeczy która mnie najbardziej irytuje jest pobieranie tych samych danych z bazy w wielu miejscach w kodzie. Powiedzmy mam taką sytuacje że mam trzy widoki wyświetlane z trzech różnych metod akcji. W każdym z tych widoków wyświetlam jakieś dane które są po części wspólne dla tych wszystkich widoków.
    Co robić w takiej sytuacji, pobrać wszystkie dane z bazy w pierwszej metodzie akcji a potem zapisać je w sesji przeglądarki ?

Co nazywasz "sesją przeglądarki"?

Co rozumiesz poprzez "te same dane"? Rekordy z jednej tabeli, czy może imię i nazwisko klienta na różnych ekranach aplikacji, czy może np. listę typów dokumentów, którą wyświetlasz na różnych ekranach dodawania/edycji dokumentu?

Czy jest coś złego z korzystania sesji w przeglądarce/ na serwerze ?

Zużywa zasoby, jeśli się jej nie sprząta, to może zapchać serwer.

Inna sytuacja jest taka że w każdej metodzie akcji pobieram coś z bazy. Często są to te same dane co w poprzedniej metodzie. Czy takie podejście jest okej ?

Co to znaczy "poprzedniej metodzie"? Metod się raczej nie numeruje. :) Możesz dokładniej opisać o co Ci chodzi?

Ja jakoś jestem sceptycznie nastawiony do przysyłania 7 parametrów z widoku do metody z metody do widoku z widoku do metody i z metody do widoku..

7 parametrów to dużo, ale tak w ogóle, to co dokładnie masz na myśli?

Zauważyłem że w aplikacji WPF trzymają jakoś wypełnione obiekty, do których w każdej chwili mogą się odezwać i pobrać dane.

Bo aplikacje WPF to aplikacje działające na jednym komputerze, więc obiekty GUI mają dostęp do obiektów danych, bo są w jednej pamięci.
Aplikacje webowe pracują po dwóch stronach kabla, więc przeglądarka użytkownika nie jest w stanie trzymać po prostu referencji do obiektu w pamięci serwera.

  1. To pytanie jest bardzo ogólne. Chodzi o to jak pisać takie duże aplikacje ASP.NET MVC, bo u nas większość robiona jest w kontrolerach -> strzał do serwisu -> baza -> odpowiedź. W efekcie czego robi się rozpierdol.

No generalnie tak działać takie aplikacje powinny. Z tego opisu nie wynika, że cokolwiek jest źle.

Szczególnie jak metoda akcji ma się zachowywać w różnoraki sposób w zależności od sytuacji.

Być może potrzeba więcej metod akcji, albo więcej kontrolerów.

Wiem że to bardzo szeroki temat itd, ale bardzo chciałbym poznać jakieś małe rzeczy jakieś Wasze zwyczaje np, jakie tworzycie sobie dodatkowe foldery/ klasy do czego służą. Może jakim wzorcom powinienem się zainteresować.

Generalnie aplikacje dzieli się przede wszystkim na warstwy. To bardzo szeroki temat, ale dla prostych, crudowych aplikacji z prostą logiką, w zupełności wystarcza podejście kontroler <-> serwis aplikacyjny <-> kontekst ORM.
Klasy tworzy się wg potrzeb, a foldery, żeby pogrupować klasy.

  1. Jeżeli metoda akcji zwraca mi model widoku w którym mam kilka modeli, to do zwracanego modelu do nazwy dodaję viewmodel natomiast do składowych klas tylko model czy takie podejście jest okej ?

Jeśli ta konwencja jest dla Ciebie czytelna, to chyba ok. Chociaż ja pewno bym wszystko viewmodelami nazywał.

  1. Powiedzmy że tworzę sobie metodę która mi zwraca dwie, trzy albo n rzeczy. I teraz mam pytanie, czy mam tworzyć nową klasę i zwrócić jej obiekt z tymi danymi? Czy tak się powinno robić ? Czasami myślałem żeby zwrócić jakiegoś tupla albo jakąś listę jak chciałem zwrócić dwie rzeczy z metody ale nie wydawało mi się to zbyt fajne.

Lepiej klasę.

  1. Jak sobie radzić z ifami, czy to jest zło ? Bo spotkałem się ze stwierdzeniem że if w ifie to max a co jak mam tego więcej ?

Z ifami można sobie radzić za pomocą wzorców: strategia, metoda szablonowa, łańcuch odpowiedzialności, albo czasem po prostu dzieląc klasy/metody na mniejsze i wywołując oddzielnie.
Czytanie i testowanie kodu najeżonego ifami to niepotrzebna strata czasu.

  1. Jeżeli mam metodę która mi zwraca jakiś string, na podstawie którego rozpoznaje co powinienem dalej zrobić, to czy nie lepiej zwrócić go jako enum ? Wiadomo to łańcuch znaków łatwo o literówkę, czy może zwracać cyfry (1, 2, 3) i np. jeżeli zwróciło 1 zrób to, 2 to to itd.

Oczywiście enum lepszy. :)

@somekind bardzo proszę Was o jakiś komentarz.

Nie stawiaj spacji przed znakami zapytania, a "bynajmniej" to nie to samo co "przynajmniej". ;)

Zimny Kot napisał(a):

może podpowiem, abyś zainteresował się Windows Workflow Fundation silnikiem różnych tworów biznesowo-kodowych co pozwala jednym i drugim jakoś znajdować wspólny temat...

Znam jakieś 100 firm tworzących projekty w .NET. W tym jedną korzystającą z Workflow Fundation, na dodatek odchodzącą od niego, bo to jest produkt, który wybitnie Microsoftowi nie wyszedł.

2

Bo spotkałem się ze stwierdzeniem że if w ifie to max

Bo kody najeżone ifami są:

  • Nieczytelne
  • Trudne w debugowaniu (bo trudniej panować nad przepływem sterowania i rozumowac o kodzie, jeśli masz w jednej funkcji np. 10 różnych scenariuszy jak się funkcja może zachować - patrz: złożoność cyklomatyczna)
  • Trudne w testowaniu (w idealnej sytuacji każdą gałąź ifa powinieneś osobno przetestować)
  • z moich obserwacji Ify nie zawsze, ale dosyć często świadczą o zduplikowanym kodzie/zduplikowanej logice (bo wtedy kiedy ktoś zamiast napisać 5 linijek kodu, który obsługuje wszystkie przypadki, robi 20 różnych ifów, które różnią się jednym parametrem. Bardzo często widuję taki kod pisany przez mało wprawnych programistów)
  • Albo o programowaniu na zasadzie obsługi kolejnych corner case'ów, wyjątkowych sytuacji do obsłużenia. O ile corner case'y czasem trzeba obsłużyć, to całościowe programowanie w ten sposób nie jest raczej fajne. Raczej należy programować tak, żeby za pomocą krótkiego, uniwersalnego i prostego kodu obsłużyć wiele sytuacji, a nie odwrotnie - każdą pojedynczą sytuację obsługiwać ifem.

Szczególnie, że prosty i uniwersalny kod to taki, który jest w stanie obsłużyć nawet nowe sytuacje, o których programista nawet nie pomyślał wcześniej. Kod na ifach może obsłużyć tylko te przypadki, o których programista pomyślał wcześniej.

Co oznacza, że kod jest czesto w trudny w rozszerzaniu, łamie zasadę open/closed. Bo często żeby dodać nową funkcjonalność w kompletnie pobocznym module(albo klasie potomnej itp.), trzeba ruszyć kod rdzenia projektu (albo np. kod klasy bazowej) i tam np. dodać kolejnego ifa, albo usunąć ifa, który stawiał sztuczne ograniczenie.

Ify to trochę jak biurokracja. Pozwalaja na coś, albo zakazują czegoś przy spełnieniu pewnych warunków i nikt się nie łapie w tym już do końca, oprócz prawników.

Chociaż czasem ify są potrzebne. Zależy od dziedziny programowania. Można wiele mówić o czystym kodzie, ale są sytuacje kiedy to właśnie ify są najbardziej naturalnym sposobem programowania (ify są do sprawdzania warunków, a co jeśli programujesz coś, co wymaga sprawdzania ciągle jakichś warunków? Np. w jakichś algorytmach. Na siłę nie ma co unikać ifów).

a co jak mam tego więcej ?

  1. Albo zrefaktoryzować
  2. Albo skasować całą funkcję i przepisać od nowa. Bo często najeżenie ifami świadczy o nieprzemyślanym kodzie (nikt nie jest bez winy, sam właśnie kilkanaście minut temu zobaczyłem, że w jednym moim projekcie napisałem zagnieżdżonego potrójnego ifa. Chociaż na szczęście w ifach tych sprawdzam jedynie czy kolejne obiekty istnieją, więc nie jest to żadna zaawansowana logika).
  1. Jak sobie radzić z ifami, czy to jest zło ?

To są chwasty. Jak masz jednego ifa, potem dodajesz drugiego, trzeciego, kolejne zagnieżdżenia itp. Aż w końcu nie zostaje nic innego jak przepisać od nowa.

1
  1. Jak sobie radzić z ifami, czy to jest zło ? Bo spotkałem się ze stwierdzeniem że if w ifie to max a co jak mam tego więcej ?

Dla mnie takie gadanie nie ma sensu. To tak jak powiedzieć, że rzucanie wyjątków to zło. Wszystko zależy od kontekstu.

Generalnie jednak, jeżeli masz duże zagnieżdżenia to znaczy, że coś faktycznie jest nie tak. Jeżeli dodatkowo kod zagnieżdżony rozciąga się na więcej niż powiedzmy 10 linijek to zdecydowanie jest nie tak. Ja w swoim kodzie nie mam takich potworków nawet się o to nie starając. Podejrzewam, że takie cuś eliminowane jest na poziomie projektowania klas.

Co zrobić jeżeli masz taki kod? Trudno powiedzieć tak teoretycznie. Na pewno pomoże podzielenie kodu na mniejsze metody, być może wydzielenie jakiejś klasy.

0
RideorDie napisał(a):
  1. Jak sobie radzić z ifami, czy to jest zło ? Bo spotkałem się ze stwierdzeniem że if w ifie to max a co jak mam tego więcej ?

Przepisujemy obecnie część funkcjonalności ze starego systemu opartego o ASP.NET WebForms do innej aplikacji w nieco nowszej technologii. Mam kwiatki takie, że mam po 7 zagęszczonych instrukcji warunkowych z dodatkowymi else if, else. Nie chcesz wiedzieć jak takie coś się czyta.

1

ad1.
To zależy ;-)
Rozumiem, że masz trzy różne akcje kontrolera ( albo kilku kontrolerów, nie istotne teraz), które pokazują te same dane (plus ewentualnie jeszcze jakieś inne). Więc użytkownik klikając po stronie kilka razy pobiera to same dane z bazy. To jest całkiem OK podejście wyjściowe. Czy jest dobre i się sprawdza... to zależy

Połączenie z bazą może być wąskim gardłem, więc chcąc to zoptymalizować, możesz wtedy dopiero gdy jest taka potrzeba, zacząć to robić. Np trzymając te dane w cachu albo sesji. Jednak trzymanie czegokolwiek w sesji ma swoje problemy - musisz ją sprzątać, jak już napisał @somekind, i jeśli będzie zbyt wiele danych tam, np przez liczbę użytkowników, może Ci nie wystarczyć zasobów na serwerze. Z drugiej strony, jeśli użytkowników jest niewielu, to pewnie obciążenie bazy też nie będzie straszne - więc czy jest sens? Kolejne problemy to skalowalność. Jeśli masz tylko jeden serwer do którego się wszyscy łączą, to sesja na nim będzie się sprawdzać. Jak już masz dwa to masz dodatkowe problemy, bo teraz user musi zawsze trafiać na ten sam serwer, żeby mieć tam te dane w sesji... albo... będziesz miał je niepotrzebnie na n serwerach. Można to rozwiązać na kilka sposobów, sticki session na load balancerze albo cachując te dane... przed web serwerami (tego podejścia nie stosowałem, czytałem tylko o tym).
No i kolejna rzecz. Korzystając z jakiegokolwiek cache... musisz pomyśleć o cache invalidation, jak to się ma do Twojego systemu? Czy użytkownik może dostać dane które są sprzed 5 albo 10 minut? (zwykle, dane które są sprzed sekundy są ok, i tak mogą się zmienić milisekundy po otrzymaniu requestu przez przeglądarkę).
Kolejna sprawa - to że to są te same dane - czy rzeczywiście użytkownicy tak często je odświeżają? Może akcja 1 jest używana przez 100% użytkowników a akcje 2 i 3 to zagląda tam 1-5 % użytkowników?

Nie jest to problem, którym bym się przejmował, jeśli nie ma powodów (wydajność) ku temu.

ad2a.
Jeśli aplikacja jest duża to robienie tego w tutorialowym MVC, z folderami controllers, views i model jest ciężkie. Dużo rzeczy jest w jednym miejscu i ciężko dojść do ładu i składu.
Są frameworki które pozwalają ładnie grupować logicznie te same rzeczy razem w swoich folderach (bodajże Nancy jest tutaj przykładem). Wtedy mając np XController rzeczy związane z nim, serwisy, widoki będą w tym samym folderze, najlepiej we własnych osobnych podfolderach per logiczna akcja. "Łatwo" można zastosować takie podejście też stosując Area per funkcjonalność.

ad2b.
Co do podejścia które opisałeś brzmi ok. Jeśli nie masz w kontrolerach logiki to super. Powinna być w serwisach. Oczywiście wspomniałeś o 300 liniach... w metodzie. To już nie ma związku z MVC czy nie, tylko "zwykłym" programowaniem. Powinieneś stosować te same zasady jak zawsze, SOLID itd. Clean Code wujka Boba daje dobre spojrzenie na sprawę i kursy/artykuły o solidzie.

ad3.
Wg mnie wszystkie to viewModele, to jest bardziej spójne. Ale jak masz inną konwencję w pracy to się jej trzymaj. "Ciulowo ale jednakowo".

ad4.
Wg mnie używanie tupli jest złym pomysłem. Po to masz klasy, aby z nich korzystać. IEnumerable będzie ok. Względnie lista, jak chcesz ją potem zmieniać.

ad5.
Tak jak napisał @somekind. Do tego if w ifie? Fuj. Do tego poczytaj (google) o arrow antipattern. Ta sama sytuacja co wcześniej: Clean Code i SOLID :)

ad6.
Enum. Tak samo w parametrach metod, jeśli tylko ma to sens.

1

Są takie dwa proste triki co duzo ułatwiają pisanie. Pierwszy jest taki że każdy obiekt ma taką właściwość ze koniec końców zapisujesz go do jakiejś kolekcji listy tablicy itd, zamiast miast korzystać z prymitywnych kolekcji i pisać tysiąc metod filtrujących piszesz wyspecjalizowaną kolekcje. Wyobraź że w jakieś nie trywialnej klasie musisz mieć współbieżną funkcje do odtwarzania poprzednich operacji przód/tył lub dodaj nową . Możesz spróbować dodać dwa stacki i lock'mi to jakoś posklejać do kupy, trochę zagmatwane ale do zrobienia. Problem jest taki że przewijanie nie jest takie proste i może przebiegać na wiele rożnych sposobów, czy wolno modyfikować w trakcje przwijania, jak tak to co ruchami do przodu, czy po modyfikacji pamiętać ostaniom pozycje czy wracać na początek itp, itd. Może się okazać że zamiast staków lepiej użyć linked listę jako iterator albo arrayliste i index, w tej sytuacji musisz albo zmienić strukturę i WSZYSTKIE nawet najbardziej nie winne odwołania do stacków... albo zostać przy stakach i obejść problem if'ami i mieć nadzieje że będzie dobrze. Albo że trzeba zliczać liczbe operacji powodzenia w dopisaniu bezbłądnie count++ przy każdym wywołaniu .
Ale zamiast tego możesz też napisać klase UndoRendo<T> z metodami udno rendo i add remove ukryć w nich te kolekcje, współbieżność i mieć prostą czytelną klasę. I zmieniać jej implementacje dowoli. Możesz pójść krok dalej i wydzielić interfejs z tymi metodami, mieć kilka klas i wybierać zamieniać implementacje zależnie od potrzeb. Ale tu jest taki problem ze jak chcesz zamienić z wersji A na B ale tu łatwo się pogubić bo jak się przekazuje i korzysta z intefejsu ILitera to jak mieć pewność że zawsze jest B?,
I tu przy chodzi drugi trick starasz się wszystkie pola i kolekcje robić readonly i deklarować je w jednym miejscu(w c# jest nawet konstruktor statyczny:P) z miejsca nie musisz martwić się takimi dylematami plus cały kod jest ładniejszy.

0

Co nazywasz "sesją przeglądarki"?
sessionStorage.setItem('key', value)

Inna sytuacja jest taka że w każdej metodzie akcji pobieram coś z bazy. Często są to te same dane co w poprzedniej metodzie. Czy takie podejście jest okej ?

Tu miałem na mysli że metoda akcji A wyswietla widok A, metoda akcji B wyświetla widok B. W widoku A miałem dane xyz, w widoku B również potrzebuję wyświetlić dane XYZ

Co rozumiesz poprzez "te same dane"? Rekordy z jednej tabeli, czy może imię i nazwisko klienta na różnych ekranach aplikacji, czy może np. listę typów dokumentów, którą wyświetlasz na różnych ekranach dodawania/edycji dokumentu?

No powiedzmy że trzymam sobie w sessionStorage w przeglądarce jakieś id czegoś co wybrał użytkownik. Na podstawie tego Id pobieram sobie obiekty z bazy i niektóre propertisy wyświetlam w widoku np. nazwa czegos opis cena itp. W kolejnych widokach wyświetlanych użytkownikowi również wyświetlam te podstawowe dane. Czy w takim przypadku lepiej trzymać w sesji na serwerze te dane które są wspólne dla innych widoków, czy w każdej metodzie akcji pobierać je z bazy jeszcze raz?
Ja do tej pory mam takie doświadczenia z sesją i swoje spostrzeżenia że wykorzystuje ją tylko w tedy kiedy to jest niezbędne, czyli potrzebuje trzymać jakąś informacje np. o wyborze czegoś - powiedzmy jakiś koszyk bez zapisywania tego do bazy. W innych przypadkach pobieram często te same dane w metodach akcji. Nie wiem właśnie czy takie podejście jest dobre czy nie, czy ma to jakieś duże znaczenie. Generalnie wolę sobie też wygenerować viewModel na serwerze (wszystko policzyć po stronie serwera itp) i wyświetlić te dane niż robić to w js po stronie klienta.

Mam jeszcze takie pytanie które mnie bardzo nurtuje. Wywołuje z widoku jakąś metodę akcji i potrzebuje przesłać jej jakieś parametry .Nie chcę też żeby one były widoczne w adresie URL dlatego wysyłam formularze z tymi danymi.. <form> To też jest dla mnie sick ale nie wiem może tak się robi, ja się czuje z tym źle ;p O wiele lepiej wygląda moim zdaniem jakby się dało zrobić przekierowanie do metody akcji js np coś w tym stylu: $.post( no ale tak się niestety nie da.

2
RideorDie napisał(a):

Co nazywasz "sesją przeglądarki"?
sessionStorage.setItem('key', value)

Czyli chcesz cachować dane po stronie klienta? To spowoduje problemy z ich odświeżaniem. Moim zdaniem to mikrooptymalizacja, która nie ma sensu w większości przypadków.

No powiedzmy że trzymam sobie w sessionStorage w przeglądarce jakieś id czegoś co wybrał użytkownik. Na podstawie tego Id pobieram sobie obiekty z bazy i niektóre propertisy wyświetlam w widoku np. nazwa czegos opis cena itp. W kolejnych widokach wyświetlanych użytkownikowi również wyświetlam te podstawowe dane. Czy w takim przypadku lepiej trzymać w sesji na serwerze te dane które są wspólne dla innych widoków, czy w każdej metodzie akcji pobierać je z bazy jeszcze raz?

Ale to operujesz w metodach akcji bezpośrednio na bazie? Bo np. dobre ORMy pozwalają na cachowanie danych. Są też inne biblioteki do cachowania. A nawet ASP.NET pozwala na cachowanie wyników akcji metod (http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs). Generalnie lepiej używać istniejących rozwiązań zamiast samemu wymyślać koło na nowo.

Ja do tej pory mam takie doświadczenia z sesją i swoje spostrzeżenia że wykorzystuje ją tylko w tedy kiedy to jest niezbędne, czyli potrzebuje trzymać jakąś informacje np. o wyborze czegoś - powiedzmy jakiś koszyk bez zapisywania tego do bazy.

Tak, to jest prawidłowy przypadek użycia sesji.

W innych przypadkach pobieram często te same dane w metodach akcji. Nie wiem właśnie czy takie podejście jest dobre czy nie, czy ma to jakieś duże znaczenie. Generalnie wolę sobie też wygenerować viewModel na serwerze (wszystko policzyć po stronie serwera itp) i wyświetlić te dane niż robić to w js po stronie klienta.

Dzięki temu masz pewność, że odczytane dane faktycznie istnieją w bazie i są aktualne. Ale tym, czy jest sens ponownie czytać dane z bazy, czy nie, powinna zajmować się warstwa dostępu do danych, nie warstwa webowa.

Mam jeszcze takie pytanie które mnie bardzo nurtuje. Wywołuje z widoku jakąś metodę akcji i potrzebuje przesłać jej jakieś parametry .Nie chcę też żeby one były widoczne w adresie URL dlatego wysyłam formularze z tymi danymi.. <form> To też jest dla mnie sick ale nie wiem może tak się robi, ja się czuje z tym źle ;p O wiele lepiej wygląda moim zdaniem jakby się dało zrobić przekierowanie do metody akcji js np coś w tym stylu: $.post( no ale tak się niestety nie da.

Trochę nie rozumiem w czym rzecz, mógłbyś podać kawałek kodu, który ten cały przepływ obrazuje? Bo ogólnie to chyba kwestia dodania atrybutu [Post] na akcji, do której chcesz przekierować, ale pewny nie jestem.

0

Ale to operujesz w metodach akcji bezpośrednio na bazie?

W pracy mam serwisy WCF także z nich odzywam się do bazy, a w domu do tej pory w metodach akcji odzywałem się do repozytoriów. Jak to jest z dodatkową warstwą serwisów (tych normalnych nie WCF :P), jak tego używać. To jedna z moich metod akcji małej aplikacji. Co z niej powinienem wyrzucić do serwisu ?

 

public ActionResult Index(int? displayPostCount, int? skipPostCountIndex)
        {
            List<int> userLikedPostsId;

            int _displayPostCount = displayPostCount ?? 4;
            int _skipPostCountIndex = skipPostCountIndex ?? 0;

            List<Post> posts = _postRepository.GetPostsToDisplay(_skipPostCountIndex, _displayPostCount);
            List<DisplayPostsModel> displayPosts;

            displayPosts = posts
            .Select(p => new DisplayPostsModel
            {
                PostId = p.PostId,
                Message = p.Message,
                IsCurrentlyLoggedUserPost = User.Identity.GetUserId() == p.ApplicationUser.Id ? true : false,
                PostImageData = p.PostImageData,
                UserImageData = p.ApplicationUser.UserImageData,
                UserFullName = p.ApplicationUser.FirstName + " " + p.ApplicationUser.LastName,
                PostAuthorId = p.ApplicationUserId,
                UserName = p.ApplicationUser.UserName,
                LikesCount = p.LikesCount,
                PublishDate = p.PublishDate,

            })
            .ToList();

            if (User.Identity.IsAuthenticated)
            {
                userLikedPostsId = _userRepository.GetSingleUserById(User.Identity.GetUserId()).RatePosts.Select(p => p.PostId).ToList();
                if (userLikedPostsId.Count > 0)
                    displayPosts.Where(p => userLikedPostsId.Contains(p.PostId)).ToList().ForEach(p => p.isLiked = true);
            }

            if (Request.IsAjaxRequest())
                return PartialView("_DisplayPosts", displayPosts);
            else
            {
                var viewModel = new IndexViewModel()
                {
                    DisplayPosts = displayPosts.AsEnumerable()
                };

                return View(viewModel);
            }
        }


I jak to jest np, jak wywołuję metodę akcji POST czyli np. metoda ta oczekuje na jakiś viewModel. Mam ten viewModel wysłać do serwisu i w nim zapisać te dane do bazy lub/i coś pobrać i zwrócić viewModel z serwisu ?

Trochę nie rozumiem w czym rzecz, mógłbyś podać kawałek kodu, który ten cały przepływ obrazuje? Bo ogólnie to chyba kwestia dodania atrybutu [Post] na akcji, do której chcesz przekierować, ale pewny nie jestem.

Powiedzmy że mam link po kliknięciu którego robię przekierowanie do jakiejś metody akcji która zwraca mi widok. W tym przekierowaniu potrzebuję wysłać jakieś parametry. No to jak zrobię to w ten sposób <a href="/Home/Summary?count=12&name=belt">Link</a> to adres URL wygląda tak: http://localhost:6408/Home/Summary?count=12&name=belt Tylko że powiedzmy nie chcę żeby użytkownik widział te dane u siebie w adresie. Ja nie wiem czy nie da się jakoś w RouteConfig tego zatuszować bo kiedyś chyba o tym czytałem ale powiedzmy że się nie da. No to w takim przypadku jedyną opcją jest wysłanie formularza do tej metody czyli

<form method="POST" action="/Home/Summary">
	<input type="hidden" name="count" />
	<input type="hidden" name="name" />
</form>

Czy da się to zrobić w jakiś bardziej normalny sposób ?

1

Całość wpakuj do serwisu. W kontrolerze nie powinno być logiki. Poza tym okropnie napisane(ale to osobny problem).

Tak, jak masz akcje post i chcesz zapisać do bazy (lub zrobić coś innego) to serwis powinien się tym zająć. Jeśli do tego chcesz coś zwrócić, to też serwis powinien to zwrócić.

Co do przekierowania i ukrywania parametrów, to trochę nie tak działa web:
Dlaczego chcesz w ogóle je ukrywać? URL mają prowadzić do zasobów, więc szczególnie gdy to jest akcja typu GET - pobierz mi i pokarz jakiś widok z czymś tam, dobrze jest dać użytkownikowi możliwość zapisania sobie tego adresu, żeby mógł tam wrócić lub podzielić się nim z kimś tam. Jeśli problemem jest dostęp do danego widoku (np tylko zalogowany użytkownik może mieć dostęp, albo tylko użytkownik będący właścicielem zasobu) to też ukrywanie nie jest rozwiązaniem, ale mechanizm autentykacji użytkownika.

Jeśli jest to akcja typu POST, czyli weź mi te dane z formularza, czegokolwiek, zapisz na serwerze gdzieś tam (na przykład dodaj do koszyka zamówień), to faktycznie je powinieneś schować, ale z nieco innych względów. Między innymi po to, żeby ktoś podwójnie nie wysłał tego samego (choć i tak da się, ale to inny problem). No i POST wtedy jest najlepszy i właśnie do tego służy.

Jak chcesz je ukryć w akcji typowej dla GET, bo tak Ci biznes kazał i nie przetłumaczysz, to zrobienie tego POSTem jest najłatwiejsze. Po prostu... tak się z wyżej wymienionych powodów nie powinno robić w webie. Oczywiście, można też zrobić to inaczej. Możesz np wysyłać je w ciasteczku, ale to oczywiście więcej pracy dla Ciebie z obsługą tego. Możesz ewentualnie użyć jakiegoś szyfrowania tych parametrów, tak aby nie dało się stwierdzić co tam rzeczywiście jest. Ale to nie do końca je całkiem ukryje, bo będą ale w innej formie, więc chyba nie o to pytasz.

3

Serwis aplikacyjny widziałbym tak:

 
public List<DisplayPostsModel> GetDisplayPosts(int displayPostCount, int skipPostCountIndex, bool isUserAuthenticated, int userId)
{
            List<Post> posts = _postRepository.GetPostsToDisplay(skipPostCountIndex, displayPostCount);
            List<DisplayPostsModel> displayPosts = posts.Select(p => new DisplayPostsModel
            {
                PostId = p.PostId,
                Message = p.Message,
                IsCurrentlyLoggedUserPost = userId == p.ApplicationUser.Id ? true : false, // a może jakaś metoda do tego?
                PostImageData = p.PostImageData,
                UserImageData = p.ApplicationUser.UserImageData,
                UserFullName = string.Format("{0} {1}", p.ApplicationUser.FirstName, p.ApplicationUser.LastName), // chociaż lepiej dodać właściwość FullName to klasy ApplicationUser
                PostAuthorId = p.ApplicationUserId,
                UserName = p.ApplicationUser.UserName,
                LikesCount = p.LikesCount,
                PublishDate = p.PublishDate,

            })
            .ToList();

           MarkPostsAsLiked(displayPosts,  isUserAuthenticated, userId);

          return displayPosts;
}

private void MarkPostsAsLiked(List<DisplayPostsModel> displayPosts, bool isUserAuthenticated, int userId)
{
             if (isUserAuthenticated)
            {
                var user = _userRepository.GetSingleUserById(userId);
                var userLikedPostsIds = _context.RatePosts.Where(rp => rp.UserId == userId).Select(rp => rp.PostId).ToList();
                foreach (var post in displayPosts)
                {
                     post.IsLiked = userLikedPostsIds.Contains(post.PostId);
                }
            }
}

A kontroler tak:

 
public ActionResult Index(int displayPostCount = 4, int skipPostCountIndex = 0)
        {          
           var displayPosts = _postsService.GetPostsToDisplay(displayPostCount, skipPostCountIndex, User.Identity.IsAuthenticated, User.Identity.GetUserId())

            if (Request.IsAjaxRequest())
            {
                return PartialView("_DisplayPosts", displayPosts);
            }
            else
            {
                var viewModel = new IndexViewModel()
                {
                    DisplayPosts = displayPosts.AsEnumerable()
                };

                return View(viewModel);
            }
        }

Ogólnie, to:

  1. Trzeba zdecydować się na jedną konwencję - jeśli używasz prefiksu _ dla pól klasy, to nie używaj go do zmiennych lokalnych metody.
  2. Zmienne deklaruj jak najbliżej miejsca ich użycia. (Np. List<int> userLikedPostsId.) To ułatwia późniejsze dzielenie kodu.
  3. Nie wywołuj zbyt wielu metod w jednej linii. Takie coś jest dość nieczytelne: displayPosts.Where(p => userLikedPostsId.Contains(p.PostId)).ToList().ForEach(p => p.isLiked = true);. Tak poza tym, to robienie na czymś ToList() tylko po to, żeby zrobić ForEach nie ma sensu. Czytelniej jest ze zwykłym foreach.

Kod, który napisałem, też nie jest idealny, ale nie chciałem przesadzać z refaktoryzacją:

  1. Informacje o Userze nie powinny być przekazywane z kontrolera do serwisu. Powinien zamiast tego istnieć jakiś obiekt zawierający te dane, który byłby wstrzykiwany do wszystkich serwisów, kontrolerów i innych klas i zwracał obiekt typu User.
  2. Obiekty typu DisplayPostsModel mogłyby być zwracane bezpośrednio z repozytorium.
  3. Ponieważ wartości displayPostCount i skipPostCountIndex zawsze występują razem (i jedna nie ma sensu bez drugiej), to powinny tworzyć jakąś jedną klasę.

I jak to jest np, jak wywołuję metodę akcji POST czyli np. metoda ta oczekuje na jakiś viewModel. Mam ten viewModel wysłać do serwisu i w nim zapisać te dane do bazy lub/i coś pobrać i zwrócić viewModel z serwisu ?

Z grubsza rzecz biorąc tak.

Czy da się to zrobić w jakiś bardziej normalny sposób ?

No niby tak, ale wtedy musiałbyś pisać aplikację SPA, więc raczej użyć Knockouta/Angulara/Reacta/Aureli oraz WebAPI niż ASP.NET MVC.
Ale ukrywanie linków przed userem nie ma żadnego sensu. Co więcej, jest niezwykle denerwujące, popatrz na http://rozklad-pkp.pl, w którym wyszukiwanie generuje linki w rodzaju: http://rozklad-pkp.pl/pl/tp?queryPageDisplayed=yes&REQ0JourneyStopsS0A=1&REQ0JourneyStopsS0G=5100065&REQ0JourneyStopsS0ID=&REQ0JourneyStops1.0G=&REQ0JourneyStopover1=&REQ0JourneyStops2.0G=&REQ0JourneyStopover2=&REQ0JourneyStopsZ0A=1&REQ0JourneyStopsZ0G=5100259&REQ0JourneyStopsZ0ID=&date=09.06.16&dateStart=09.06.16&dateEnd=09.06.16&REQ0JourneyDate=09.06.16&time=02%3A51&REQ0JourneyTime=02%3A51&REQ0HafasSearchForw=1&existBikeEverywhere=yes&existHafasAttrInc=yes&existHafasAttrInc=yes&REQ0JourneyProduct_prod_section_0_0=1&REQ0JourneyProduct_prod_section_1_0=1&REQ0JourneyProduct_prod_section_2_0=1&REQ0JourneyProduct_prod_section_3_0=1&REQ0JourneyProduct_prod_section_0_1=1&REQ0JourneyProduct_prod_section_1_1=1&REQ0JourneyProduct_prod_section_2_1=1&REQ0JourneyProduct_prod_section_3_1=1&REQ0JourneyProduct_prod_section_0_2=1&REQ0JourneyProduct_prod_section_1_2=1&REQ0JourneyProduct_prod_section_2_2=1&REQ0JourneyProduct_prod_section_3_2=1&REQ0JourneyProduct_prod_section_0_3=1&REQ0JourneyProduct_prod_section_1_3=1&REQ0JourneyProduct_prod_section_2_3=1&REQ0JourneyProduct_prod_section_3_3=1&REQ0JourneyProduct_opt_section_0_list=0%3A000000&existOptimizePrice=1&existHafasAttrExc=yes&REQ0HafasChangeTime=0%3A1&existSkipLongChanges=0&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&REQ0HafasAttrExc=&existHafasAttrInc=yes&existHafasAttrExc=yes&wDayExt0=Pn|Wt|%C5%9Ar|Cz|Pt|So|Nd&start=start&existUnsharpSearch=yes&came_from_form=1#focus
które po chwili wygasają, i nie da się nikomu przesłać wyników wyszukiwania.

P.S. Trochę o repozytoriach: http://commitandrun.pl/2016/05/11/Repozytorium_najbardziej_niepotrzebny_wzorzec_projektowy/

0

Stworzenie class library dla serwisów i trzymanie w tam modeli widoku to dobry pomysł ?

Jak chcesz je ukryć w akcji typowej dla GET, bo tak Ci biznes kazał i nie przetłumaczysz
Bingo :P

Poza tym okropnie napisane(ale to osobny problem)
mógłbyś jakoś rozwinąć co byś zmienił, poza tym co napisał @somekind

Jeszcze mi przyszła do głowy jedna rzecz mianowicie pisanie kodu js w znacznikach <script> w widoku. Czy to jest taką złą praktyką i czy zawsze powinienem tworzyć zewnętrzne skrypty?

Dziękuję Wam bardzo za takie fajne odpowiedzi i za przykład jak powinien serwis wyglądać!

0
RideorDie napisał(a):

Jeszcze mi przyszła do głowy jedna rzecz mianowicie pisanie kodu js w znacznikach <script> w widoku. Czy to jest taką złą praktyką i czy zawsze powinienem tworzyć zewnętrzne skrypty?

Jeśli takie skrypty mają być generowane na podstawie viewmodelu, to nawet konieczne jest umieszczenie ich w widoku. W przeciwnym razie można je przenieść do innych plików.

0

Przyjrzałem się trochę serwisom i zmodyfikowałem powyższy kod. W projekcie z serwisami stworzyłem folder z obiektami DTO.

serwis -> DTO

 
public class PostDto
{
    public int PostId { get; set; }
    public string Message { get; set; }
    public DateTime PublishDate { get; set; }
    public int LikesCount { get; set; }
    public byte[] PostImageData { get; set; }
    public string PostImageMimeType { get; set; }
    public string ApplicationUserId { get; set; }

    public static PostDto Create(Post post)
    {
        if (post != null)
            return null;

        return new PostDto
        {
            PostId = post.PostId,
            ApplicationUserId = post.ApplicationUserId,
            LikesCount = post.LikesCount,
            Message = post.Message,
            PostImageData = post.PostImageData,
            PostImageMimeType = post.PostImageMimeType,
            PublishDate = post.PublishDate
        };
    }
}
 

Serwis -> viewModels


public class DisplayPostsViewModel
{
    public List<PostDto> Posts { get; } = new List<PostDto>();
}
 

Serwis -> Concrete -> HomeControllerService

List<Post> posts = _postRepository.GetPostsToDisplay(skipPostCountIndex, displayPostCount);

            var model = new DisplayPostsViewModel();
            foreach (var p in posts)
            {
                model.Posts.Add(PostDto.Create(p));
            } 

Rozumiem że tak to powinno wyglądać ?

Obiekty typu DisplayPostsModel mogłyby być zwracane bezpośrednio z repozytorium.

To powinno być zwrócone jako obiekt DTO już z repozytorum np. DisplayPostModelDto ?

Jakiego typu obiekty powinny być zwracane z repozytoriów. Chodzi mi o to że powiedzmy mam jakąś dużą aplikacje dużo tabel i na przykład chcę żeby jedna metoda w repozytorium zwróciła mi dane z kilku tabel. Czyli nie będzie to jeden obiekt ale jakaś klasa która ma w sobie kilka klas /list klas wypełnionych obiektami. W jakiej warstwie aplikacji powinny żyć te obiekty. Czytałem ogólnie coś o tym że powinny być zwracane z repozytorium obiekty domeny, z drugiej strony mając przed oczami serwisy WCF z metodami które zwracają data kontrakty z potrzebnymi danymi to dla mnie nie ma to sensu.

Zacząłem też mieć problem z referencjami (circular dependency) podejrzewam że dlatego że nie mam za dobrze podzielonej solucji. Dlatego zapytam się co można zmienić w tej solucji (dodać, usunąć, przenieść) -> zdjęcie.

Wracając do pobierania danych z tabel też mam jedno pytanie które mnie nurtuje. Wyciągam coś z jednej tabeli i potrzebuje też coś z drugiej tabeli. Te tabele są ze sobą połączone to przez nav properties dostaje sie do danych z drugiej tabeli. To ma sens bo nie ma potrzeby żebym 2x odzywał się do bazy danych skoro zrobię to za pierwszym razem.
Co w przypadku jeżeli tabela do której się odzywam jest połączona z inną tabelą ta jeszcze z inną a ta z jeszcze inną, a ja potrzebuje dane z tej ostatniej. Nie wiem czy to ma sens ale powiedzmy że muszę przedrzeć się przez 2 tabele żeby wyciągnąć odpowiednie dane z trzeciej. Czy w takim przypadku lepiej jest zrobić to poprzez drugie zapytanie do bazy i po idku sobie wyszukać interesujące mnie rzeczy, czy zrobić to przechodząc z tabeli do tabeli przez navigation properties?

1

Ok, to co do tego kawałka:

Co do solucji - Nie pokazałeś powiązań pomiędzy projektami. Fajnie że widzę jakie są, ale nie wiem który z którym gada i w którą stronę idą zależności. Paint będzie ok żeby to pokazać ;-)

Co fajnie zrobiłeś, to metodę Create (choć ja bym ją nazwał CreateFrom). To spoko, bo zamyka ten kod w jednym miejscu. Można by tez użyć do tego konstruktora oczywiście, ale statyczne metody pozwalają nadawać nazwy, a to często pomaga podnieść czytelność kodu. Aczkolwiek cała logika dodawania do listy viewModelu, powinna się znaleźć w konstruktorze DisplayPostsViewModel a nie na zewnątrz (lub jeśli wolisz, w metodzie statycznej). Mógłby przyjmować jako parametr IEnumerable<Posts>. I tu później mi brakuje kawałka kodu z logiką zaznaczającą post jako lubiany przez danego użytkownika.

Napisałeś, że stworzyłeś folder z DTO. Czyli wcześniej był już w tym projekcie Services folder ViewModels? Co zawierał? ViewModele dla weba? Zastanów się, czy potrzebujesz osobnych klas na DTO, które mają być czymś innym niż viewModele?
To zależy od tego, czy jedynym klientem dla serwisów jest i będzie WebUI, czy są/będą inne. Szczerze, nie wydaje mi się, żeby tworzenie dodatkowo DTO było potrzebne - bo nie wydaje mi się, żeby pojawił się nagle inny klient. Wtedy i tak dla niego pewnie trzeba będzie zrobić osobne API z własnymi DTO (to samo API dla weba i np natywnych aplikacji mobilnych przy skomplikowanej logice to nie jest dobry pomysł - wydajność cierpi mocno).
Zresztą, wtedy też serwis mógłby zwracać w ogóle tylko DTO i dopiero później ViewModele były by z niego w następnej warstwie dla weba tworzone. Tak jak to wygląda teraz, opakowywanie DTO w viewModel w jedynej metodzie która z niego korzysta, to dla mnie sztuka dla sztuki.

Sądzę ,że możesz zostać przy nazewnictwie XModel i trzymać to w jednym miejscu (niekoniecznie tym samym folderze, podział projektu na podfoldery/area/inne rzeczy to osobny temat).

Jeśli tworzysz dodatkową warstwę, to dobrze było by wiedzieć po co, bo jeśli projekt to CRUD to architektura "encje na twarz i pchasz" sprawdzi się lepiej - mniej podatnego na błędy boilerplate kodu który przepisuje 1 do 1 klasy i niepotrzebnie je opakowuje tylko.

Na marginesie:

  1. Brakuje CI kawałka logiki - nie masz nigdzie w DTO pola z IsLiked.
  2. Guard jest zły, masz "jak nie jest nullem, to zwróć nulla". Chyba nie o to Ci chodziło :) Do tego, moim zdaniem jest to niepotrzebne. Programujesz defensywnie - po co? Spodziewasz się, że tam trafi null - dlaczego? W tym momencie nie ma to sensu, żeby ktoś wywołał tę metodę z nullem. Z repository nie powinien przyjść taki element kolekcji będący nullem. Jeśli jest potrzeba stworzenia pustego obiektu... to kolega z zespołu (to przed jego nullem w końcu się tam bronisz raczej), albo Ty, stworzy go sobie. Jeśli będzie chciał nulla to sobie ... weźmie nulla, nie musi używać do tego create. Programowanie defensywne i używanie nulli w kodzie ma swoje minusy - oprócz małych drawbacków wydajnościowych tworzysz niepotrzebnie kod defensywny i promujesz kiepską, pozwalającą na nulle architekturę zamiast skorzystania z alternatyw, tam gdzie ma to sens. Np wzorca NullObject.

Generalnie jedyne co zrobiłeś to opakowałeś coś co wcześniej było listą modeli we własną klasę i zmieniłeś nazwę z DisplayXModel na XDto...

Do tego nazwa HomeControllerService - jak już mamy wydzielony serwis, to nazwijmy go odpowiednio do tego co robi, a nie w jakiej klasie/dla jakiej klasy to robi. Poza tym, że możesz go chcieć użyć poza Home, to zwiększy czytelność. Poprzednia nazwa od @somekind PostsService brzmi rozsądnie.

2
 
public class PostDto
{
    public int PostId { get; set; }
    public string Message { get; set; }
    public DateTime PublishDate { get; set; }
    public int LikesCount { get; set; }
    public byte[] PostImageData { get; set; }
    public string PostImageMimeType { get; set; }
    public string ApplicationUserId { get; set; }

    public static PostDto Create(Post post)
    {
        if (post != null)
            return null;

        return new PostDto
        {
            PostId = post.PostId,
            ApplicationUserId = post.ApplicationUserId,
            LikesCount = post.LikesCount,
            Message = post.Message,
            PostImageData = post.PostImageData,
            PostImageMimeType = post.PostImageMimeType,
            PublishDate = post.PublishDate
        };
    }
}
 

Jeżeli PostDto jest świadom istnienia Post, to znaczy, że dllka z PostDto musi mieć referencje do dllki z Post oraz innych bibliotek, których ta klasa wymaga. W efekcie serwisy muszą mieć referencję do ORMa, co jest niepoprawne architektonicznie. Do konwersji z jednego typu na drugi powinieneś napisać albo oddzielną klasę, albo użyć jakiejś biblioteki np. AutoMapper albo ValueInjecter.

RideorDie napisał(a):

Serwis -> Concrete -> HomeControllerService

Concrete czyli Beton? Chyba nie o to chodziło.
Serwisy nie wiedzą nic o kontrolerach, zresztą w ogóle może ich nie być. Co daje nazwanie klasy ControllerService? Przecież jeden kontroler może korzystać z pięciu różnych serwisów.

Nie bardzo rozumiem tego podziału na DTO/ViewModele u Ciebie. Jeśli coś wyświetlasz, to jest viewmodelem, może się on składać z innych viewmodeli. DTO raczej sugeruje transfer danych między różnymi aplikacjami.

To powinno być zwrócone jako obiekt DTO już z repozytorum np. DisplayPostModelDto ?

Ja bym tak zrobił. Tylko taka klasa nie jest wtedy żadnym repozytorium, a po prostu jakimś źródłem danych.

Jakiego typu obiekty powinny być zwracane z repozytoriów. Chodzi mi o to że powiedzmy mam jakąś dużą aplikacje dużo tabel i na przykład chcę żeby jedna metoda w repozytorium zwróciła mi dane z kilku tabel. Czyli nie będzie to jeden obiekt ale jakaś klasa która ma w sobie kilka klas /list klas wypełnionych obiektami. W jakiej warstwie aplikacji powinny żyć te obiekty. Czytałem ogólnie coś o tym że powinny być zwracane z repozytorium obiekty domeny, z drugiej strony mając przed oczami serwisy WCF z metodami które zwracają data kontrakty z potrzebnymi danymi to dla mnie nie ma to sensu.

Czytałeś post na blogu, który podlinkowałem wcześniej?

Repozytoria mają sens w przypadku logiki biznesowej, a nie przesyłania danych przez WCF. Stosując DDD myślisz obiektowo - np. nie zmieniasz bezpośrednio ulicy klienta o jakimś id, tylko pobierasz z repozytorium obiekt Customer, a potem wołasz customer.Address.ChangeStreet("Zielona");.
Ale repozytoria to nie jest dobry mechanizm w przypadku CRUD, ani odczytywania dużych ilości rekordów na potrzeby grida.

Zacząłem też mieć problem z referencjami (circular dependency) podejrzewam że dlatego że nie mam za dobrze podzielonej solucji. Dlatego zapytam się co można zmienić w tej solucji (dodać, usunąć, przenieść) -> zdjęcie.

Na pewno brakuje Ci projektu Contracts na DTO/ViewModele i interfejsy serwisów. Do niego referencje powinny mieć Services i WebUI.

Wracając do pobierania danych z tabel też mam jedno pytanie które mnie nurtuje. Wyciągam coś z jednej tabeli i potrzebuje też coś z drugiej tabeli. Te tabele są ze sobą połączone to przez nav properties dostaje sie do danych z drugiej tabeli. To ma sens bo nie ma potrzeby żebym 2x odzywał się do bazy danych skoro zrobię to za pierwszym razem.
Co w przypadku jeżeli tabela do której się odzywam jest połączona z inną tabelą ta jeszcze z inną a ta z jeszcze inną, a ja potrzebuje dane z tej ostatniej. Nie wiem czy to ma sens ale powiedzmy że muszę przedrzeć się przez 2 tabele żeby wyciągnąć odpowiednie dane z trzeciej. Czy w takim przypadku lepiej jest zrobić to poprzez drugie zapytanie do bazy i po idku sobie wyszukać interesujące mnie rzeczy, czy zrobić to przechodząc z tabeli do tabeli przez navigation properties?

Jeśli chcesz programujesz obiektowo, to rób to przez właściwości.
Jeśli chcesz programować proceduralnie, to rób po id. Niby wydajniej, ale za to trudniej testować i dbać o spójność danych w aplikacji.

AreQrm napisał(a):

Jeśli tworzysz dodatkową warstwę, to dobrze było by wiedzieć po co, bo jeśli projekt to CRUD to architektura "encje na twarz i pchasz" sprawdzi się lepiej - mniej podatnego na błędy boilerplate kodu który przepisuje 1 do 1 klasy i niepotrzebnie je opakowuje tylko.

Wyrzucanie bazy na ekran nie sprawdza się nigdy, bo nigdy nie trzeba wyświetlić danych danego rekordu na ekranie, nigdy też nie ma potrzeby zapisu wszystkich kolumn w tabeli, a przeważnie w gridach wyświetla się nie jeden rekord tylko projekcję kilku. "Encja na twarz i pchasz" to prosta droga do problemów wydajnościowych i przypadkowego odczytywania pół bazy na raz metodą select n^x + 1.
Sposobem na kod przepisujący 1:1 są gotowe biblioteki, więc to nie stanowi problemu.

0

tak jak to wygląda teraz, opakowywanie DTO w viewModel w jedynej metodzie która z niego korzysta, to dla mnie sztuka dla sztuki.

Generalnie mam takie samo wrażenie ale myślałem że takie podejście jest poprawne dlatego że zawsze słyszę że takie przepisywanie danych jest dobre, bo różne warstwy, i może i więcej kodu, więcej czasu, i wszystkiego więcej to tak się powinno pisać aplikacje, więc pomyślałem że tak jest i w tym przypadku ;). Zresztą przeczytałem też, że serwisy powinny zwracać Dto.

Załóżmy że mam dwóch klientów Web i aplikacje mobilną. Serwisy i metody w serwisach są wspólne dla tych dwóch aplikacji. Czyli w takiej sytuacji tak jak napisał @AreQrm metody w serwisach powinny zwracać Dto. Jak nazywa się wtedy ta dodatkowa warstwa w której mapuje się Dto na viewModele dla Web i na viewModele? dla aplikacji mobilnej? Rozumiem że tu zwracane dane mogą nie być takie same, bo np na aplikacji mobilnej nie chcę wyświetlać wszystkich rzeczy które mam na webie dlatego potrzebne są różne dto dla tych dwóch klientów ?

Generalnie z tego co rozumiem to działa tak że jak mam aplikacje mobilną to ona wysyła zapytanie do API które kontaktuje się z moim serwisem z którego korzysta też Web, serwis odzywa się do bazy, zwraca potrzebne dane, a API wysyła je do mojej aplikacji mobilnej np. w Json?

@somekind czy w tym projekcie Contracts powinny znajdować się wszystkie interfejsy jakie mam w aplikacji, np interfejsy do repozytoriów albo do mojego DbContext, czy tylko to co wymieniłeś ?

1

Tu nie chodzi o przepisywanie danych samo w sobie, ale o SRP i SOC, czyli oddzielanie różnych warstw (logicznych bądź fizycznych) od siebie.

Mianem DTO określamy obiekty do komunikacji z zewnętrznymi systemami albo np. plikami. ViewModel to tak naprawdę taki DTO na potrzeby widoku, z tą różnicą, że może mieć jakąś prostą logikę prezentacyjną - np. wyświetlić sumę kwot w zamówieniu, albo zrobić FullName z imienia i nazwiska. W praktyce czasem robi się tak, że ViewModel dziedziczy z DTO, żeby dodać taką dodatkową logikę. Ale robienie oddzielnej warstwy obiektów i mapowanie z DTO na VM, to dla mnie przerost formy nad treścią.
Niemniej jednak, jeśli bardzo chcesz, to możesz tak robić, ale raczej w aplikacji klienckiej. Ponieważ odpowiedzialnością takiej warstwy jest bycie klientem serwisu webowego, to sensowną nazwą może być ServiceClient.

RideorDie napisał(a):

@somekind czy w tym projekcie Contracts powinny znajdować się wszystkie interfejsy jakie mam w aplikacji, np interfejsy do repozytoriów albo do mojego DbContext, czy tylko to co wymieniłeś ?

Nie, nie wszystkie, tylko to, co łączy klienta z serwisami. U Ciebie będą to prawdopodobnie katalogi: DTO, ViewModels i Abstract z projektu Services.

0

Rozrysowałem referencje pomiędzy projektami w wyżej wymienionej solucji, co tutaj musiałbym zmienić, żeby to było poprawnie napisane ?

Póki co muszę mieć referencje z WebUI do DataAccess i Domain bo używam projektu z ASP.NET Identity, i musiałbym to jakoś przerobić.

1

Nie rozumiem, czemu masz Domain w DAL, skoro Domain == Business Logic.

WebUI powinno mieć wyłącznie referencje do Contracts. Referencję do Contracts powinno mieć także BLL, a kontener IoC w WebUI powinien być tak skonfigurowany, żeby wziąć dla Contracts implementacje z BLL.

Repositiories Contracts powinny być znane wyłącznie Serwisom i Repozytorium, nie powinny być na pewno w DAL. W ogóle nie rozumiem czym ten DAL u Ciebie do końca jest, ani co tam masz...

1 użytkowników online, w tym zalogowanych: 0, gości: 1