Dobre praktyki podczas tworzenia aplikacji kilka pytań

Odpowiedz Nowy wątek
2016-06-01 21:11
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.

Pozostało 580 znaków

2016-06-08 22:01
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.


Pozostało 580 znaków

2016-06-09 02:54

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/0[...]potrzebny_wzorzec_projektowy/


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."
zdecydowanie mój kod po Twoich przeróbkach jest 2x bardziej czytelny! Zrobię jeszcze to o czym pisałeś, dodam serwisy, zrefraktoryzuje cały kod, i zobaczę co z tego wyjdzie ;) - RideorDie 2016-06-09 22:37
Jeśli chcesz, możesz się później podzielić kodem w dziale "Oceny i recenzje". - somekind 2016-06-10 00:38

Pozostało 580 znaków

2016-06-09 19:41
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ć!

edytowany 2x, ostatnio: RideorDie, 2016-06-09 21:22

Pozostało 580 znaków

2016-06-09 21:29
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.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2016-06-11 13:07
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?

edytowany 1x, ostatnio: RideorDie, 2016-06-11 13:16

Pozostało 580 znaków

2016-06-11 19:42
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.


Pozostało 580 znaków

2016-06-11 20:25
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.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2016-06-11 22:24
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ś ?

Pozostało 580 znaków

2016-06-12 17:49
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.


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2016-06-25 11:36
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ć.

edytowany 1x, ostatnio: RideorDie, 2016-06-25 11:42

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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