Pytanie o serwisy infrastrukturalne, modelowanie obiektu i bezstanowość

1

Od kilku lat programuję w javie, ostatnio pooglądałem trochę konferencji, poczytałem tematów, postanowiłem zmienić podejście. Mam kilka pytań:

  1. Załóżmy, że mamy moduł "Weather", który pozwala nam sprawdzić pogodę dla współrzędnych geograficznych. Mamy jakąś fasadę, która wywołuję metody z serwisu infrastrukturalnego WeatherProvider. Konkretnie dane na temat pogody bierzemy z jakiś KILKU zewnętrznych API, z racji tego, że chcemy te dane jakoś scalać, jeśli nie wszystko jest podane, albo no mamy inny cel w tym, że pogodę bierzemy z kilku API a nie jednego. I teraz w czym jest moja zagwozdka. Załóżmy, że w module Weather mamy dtosa WeatherDto, który prezentuje pogodę i jest zwracany przez wspomniany wcześniej interfejs infrastrukturalny. Intefejs ten jest gdzie indziej implementowany przez kilku klientów API. Tylko sprawa jest taka, że każde API zwraca inną strukturę danych, więc każda implementacja tego intefejsu będzie musiała też zawierać logikę konwersji między zwracaną z API strukturą a moim WeatherDto. No i moje pytanie brzmi czy takie podejście jest ok. Bo oddzielamy się totalnie od klientów API i modułu Weather nie interesuje w ogóle to co zwracana każde z API - logika konwersji na WeatherDto znajduję się w implementacji interfejsu infrastrukturalnego WeatherProvider.
    A może lepiej zrobić tak, że dla każdego API zrobić osoby interfejs, który zwraca inną strukturę danych i te konwertery na WeatherDto potraktować jako logikę domeny ? Tylko no wtedy domena pogody jest związana jakoś z implementacją i światem zewnętrznym.

  2. Do tej pory pisząc RESTy wyglądało to mniej więcej tak, że logika była w serwisach, były jakieś assemblery czy konwertery no i niby wszytko ok, ale budować aplikację zaczynałem od zbudowania encji relacyjnej bazy. Po większej edukacji poczytałem o prawilnym podejściu, że najpierw powinno się budować logikę domeny, a encje bazodanowe powinny być na końcu jako rozwiązanie problemu. Podobnie, że serwisy i wszelkie assemblery to przeczenie obiektowości i pisanie kodu strukturalnego. Zacząłem zatem pisać sobie edukacyjnie jakąś gierkę strategiczno ekonomiczną zaczynając od domeny i strając się nie pisać kodu strukturalnego. Zrobiłem zatem np klasę Building prezentującą budynek z jego kosztem, poziomiem, czasem budowania itd. Nie ma żadnych geterów i seterów - posiada tylko metody biznesowe i wygląda to wszystko ok, ale bardzo dla mnie nieintuicyjne jest to, że ten obiekt jest niby stanowy, jest biznesową encją, ale przecież jego cykl życia to jedno zapytanie http :D I flow wygląda tak: muszę gdzieś pobrać info na temat tego budynku z jakiejś bazy, stworzyć go przez konstruktor, wykonuję biznesową metodę, która zmienia jego stan, czyli np sprawdzam czy można zbudować, rozbudowuję, wyciągam z niego dtosa, robię save w bazie i koniec. Czy to tak powinno wyglądać ?

  3. Trochę pytanie powiązane z pkt 2. Mam tą klasę Building. No i doszedłem do tego, że każdy budynek ma inne funkcje - np jedne są kopalniami, jedne produkują jednostki wojskowe, jedne coś przechowują i co tam dalej jeszcze wymyślę. Oczywiście nie będę robił dziedziczenia. Klasa Building zawiera podstawowe dane dla każdego budynku - ValueObjecty typu koszt, czas, nazwa. Resztę jego rzeczy typu to co ma robić, to co potrafi postanowiłem zrobić za pomocą kompozycji .. no i to zrodziło problemy. Mogę np zrobić intefejs Extractable i jego konkretną implementację + implementację NullObjectPattern i tą konkretną powstrzykiwać do jakiś kopalni, a Nullową do Building typu stocznia czy coś, ale jakoś średnio mi się to podoba. Oczywiście taki intefejs Extractable miałby metody typu extract(long dt).. ale jak potem taki Building skompowany zapisać do tabelki w bazie danych ha :D Trochę błądzę .. jakieś rady ?

1
  1. Ja bym mapował po stronie mikroserwisu który pobiera dane z danego endpointu. Tzn każdy z tych twoich serwisów wystawia identyczne API i zwraca takie samo DTO. Dzięki temu jak któryś zewnętrzny serwis się zmieni, to modyfikujesz tylko ten jeden miroserwis, a nie jakis Core twojej usługi.
  2. o_O A pomyślałeś może o tym, że gra się powinna toczyć i siedzieć w pamięci a nie wczytywać od zera przy każdym requeście? Wyobraź sobie że w strzelance ktoś stosuje takie podejście jak twoje -> za każdym razem jak jest user action (ruch myszki czy kliknięcie czy input z klawiatury) to gra wczytuje cały stan od zera, wykonuje akcje a potem zapisuje stan. Wyobrażasz sobie jaki byłby performance? ;)
  3. Ja bym prędzej zrobił generyczny interfejs Action i np. przechowywał w tych budynkach listę akcji do wykonania, a nie robił miliona property z których większość nullowa.
0

Dlaczego serwisy to przeczenie obiektowości?

0

@scibi92 bo tak już jest. Serwis jest bezstanowy i dostaje niemutowalne obiekty jako dane i zwraca kolejny zestaw niemutowalnych obiektów jako wyniki, zwykle w postaci prostych DTO. Na dobrą sprawę zwykle metody takiego serwisy mogłyby być nawet i statyczne. To wszystko to jest definicja programowania proceduralnego lub nawet funkcyjnego, a z obiektowością nic wspólnego nie ma. Trik polega na tym co się dzieje "pod spodem", tzn w jaki sposób serwis realizuje swoją logikę, bo dopiero tutaj pojawia się warstwa domenowa i obiektowość. Ale jak ktoś klepie generic cruda, to nigdy tej warstwy nie ma, bo serwis ciągnie dane z jakiegoś dao i tyle ;)

0

@Shalom: w jaki sposób serwisy są bezstanowe? Przecież posiadają referencje do jakiś DAO czy innych serwisów i innych obiektów.

Np. UserRegisterService będzie miał UserRepository, PasswordEncoder i NotificationService

1

Ale wg mnie te pola o których mówisz @scibi92 są i tak z definicji finalne i się nie zmieniają. Każde wywołanie jakiejś metody serwisowej nie ma żadnych danych odnośnie poprzednich wywołań czegoś z tego samego serwisu.

0

No to wedlug Ciebie jak obiekt jest niemutowaly to nie może mieć nic wspolnego z OOP? :D :D :D

1

No ok, a jakie możesz wyróżniać przykładowe stany takiego serwisu ? Tak jak wspomniałem - cokolwiek serwis wykonał w przeszłości - obecne wywołanie jego metod nie wie nic o tym - jak z http.

0

@Shalom:

Co do punktu drugiego. Chodzi mi konkretnie o taką grę jak ogame. Załóżmy jest takie flow:

  • użytkownik chce zupgradować kopalnie metalu, zatem idzie request : /militaryBase/jakasNazwaBazy/building/METAL_MINE/upgrade
  • z bazy jest wczytywany dany militaryBase, na jego podstawie tworzę obiekty domenowe
  • sprawdzam na obiektach domenowych, czy można zbudować, w przypadku sukcesu startuję rozbudowę, przełączam stan na "budowany", odejmuję surowce i co tam jeszcze ( wszystkie metody wywoływane są na obiektach domenowych, a nie w serwisie na dtosach)
  • zapisuję zmiany do bazy: repository.save(militaryBase.dto())

W jaki sposób przy takiej grze chcesz wykonywać w pamięci i trzymać stany bazy w pamięci ?

0

@Niezadowolony orzeł o_O a gdzie widzisz problem? Mógłbyś przecież wczytać stan gry kiedy user się loguje i potem trzymać ten stan w pamięci i na nim operować, a z jakimś timeoutem jeśli dany stan jest nieużywany to go zapisujesz i usuwasz z pamięci. Wyobraź sobie że ktoś pisze w twoim stylu dowolny program, edytor tekstu na przykład. Za każdym wciśnieciem literki wczytujesz od nowa plik a potem zapisujesz? :D

0
Niezadowolony orzeł napisał(a):

Od kilku lat programuję w javie, ostatnio pooglądałem trochę konferencji, poczytałem tematów, postanowiłem zmienić podejście. Mam kilka pytań:

  1. @Shalom dobrze Ci doradził.

  2. Co masz na myśli pisząc "cykl życia to jedno zapytanie http"?

Cykl życia budynku to cykl życia "biznesowy", a nie techniczny :) Oznacza to, że masz z tym cyklem związane:

  • stany: np. Planowany, W budowie, Budowa zakończona, W gotowości, Uszkodzony, W naprawie, Atakowany (co tam sobie wymyślisz)
  • przejścia między stanami

Za cyklem życia może stać maszyna stanów reagująca na zdarzenia, które mogą powodować przejście z jednego stanu do drugiego, zaś przejścia między stanami mogą wywoływać jakieś akcje.

np. Atakowany + zdarzenie: EventMissileIncoming może spowodować wyzwolenie logiki typu:

  • oblicz uszkodzenia
  • jeśli energia - uszkodzenia < paliSie -> przejdź do stanu "Plonie"

Ja pewnie bym szedł w takiej grze w modelowanie obiektów przez dodawanie im:

  • maszyn stanów reagaujących na dostarczone zdarzenia
  • akcji które mają być wykonywane w odpowiedzi na nadchodzące zdarzenia
  • properties pozwalających na wizualizację (np. kolor, typBudynku, itd. )
0
scibi92 napisał(a):

No to wedlug Ciebie jak obiekt jest niemutowaly to nie może mieć nic wspolnego z OOP? :D :D :D

Nie to miał na myśli - niemutwalne obiekty są spoko, jeżeli to są jakieś struktury danych, ale serwisy na zasadzie - weź jednego DTO, coś tam zrób, wypluj drugiego DTO to podejście strukturalne. Nie ma w tym nic złego, dopóki cała twoja aplikacja tak nie wygląda. Tfu, nadal nie ma w tym nic złego, ale OOP to to nie jest ;)

0

Zaraz, ale w każdej aplikacji napisanej w OOP są obiekty które porozumiewają się za pomocą wiadomości. Nie wiem w każdym razie kiedy już moge rozdzielić.
Skoro występuje abstrakcja, hermetyzacja i poliformizm to to jest chyba OOP? No nie wiem, jakoś np. prywatne metody czy istnienie kontraktów to bardziej z OOP kojarzy...
Ale ja może sie nie znam...

0

Tak masz rację, ale jeżeli do tego zastosujesz anemic entities - to IMHO tak średnio. Z drugiej strony, w prostych CRUDach czy czasem w prostych domenach i tak będzie to tak wyglądało, bo obiekty nie mają zbyt wiele zachowań, ale jeżeli masz złożoną domene/biznes i i DTOsy i serwisy no to moim zdaniem po prostu można lepiej. To co mam na myśli to prostu jeżeli masz DTOsa, w serwisie go wybebeszasz ze wszystkich pól, coś tam robisz, pakujesz do kolejnego DTOsa, w kolejnym serwisie to samo to to wtedy tak trochę c**** nie hermetyzacja. Prosty przykład o co mi chodzi co uważam, za złe - masz obiekt, który może być aktywny lub nie i zamiast np. myCoolObject.activate() robisz myCoolObject.setActive(true), tak samo dla bardziej skomplikowanych zachowań nie enkapsuluje się zachowania w obiekcie, tylko zawsze jest jakiś magiczny serwis, który setterami naparza we wszystko co się rusza

0

@scibi92 Chodzi zapewne kwestie związane np. side effect, czy stopnia komplikacji testów w OOP, czego nie ma w funkcyjnym paradygmacie. Może to kwestia architektury, że istnieją inne rozwiązania niż tylko "encja na twarz i pchasz", np. Event Driven Design - programowanie reaktywne.

0

@Shalom:

Twierdzisz, żeby nie zapisywać od razu w bazie, tylko w pamięci.

A jeśli serwer padnie i ważne zmiany (np. wybudowanie ważnego budynku) nie zostaną zapisane do bazy czy coś ?

Myślisz, że w takim Ogame, żadna akcja typu wybudowanie, update czegoś itd. nie idzie od razu do bazy ?

PS. Sory za odkopanie starego wątku, ale tak z nudów w pracy sobie przeglądam, natrafiłem na to i sam miałem ostatnio podobną rozkminę, że przecież każdy request to jakby osobna historia, więc się zastanawiałem, gdzie w takim requeście jest miejsce na rozbudowaną logikę domenową skoro to trwa tak szybko.

2

@Bambo nie rozumiesz. Jedno z drugim nie ma nic wspólnego. Czym innym jest prowadzenie gry na podstawie danych w pamięci a czym innym jest periodyczne zapisywanie stanu. Jedno drugiego nie wyklucza.
Czy jak edytujesz plik w Wordzie to myślisz ze za każdym razem jak wpiszesz literkę to dane są zapisywane na dysk i z niego wczytywane? Czy jednak stan jest w pamieci i na nim operujesz, a w tle działa proces który co jakiś czas zapisuje dane (lub też pewne eventy powodują zapisanie stanu)?

Idea jest właśnie taka, ze w stan aplikacji jest w pamięci, ale jest co jakiś czas zapisywany. Możesz zapisywać stan, czy też eventy które go zmieniają, bez różnicy, ale chodzi o to żeby nie pisać i czytać non stop bo to nie ma sensu.

0
Bambo napisał(a):

A jeśli serwer padnie i ważne zmiany (np. wybudowanie ważnego budynku) nie zostaną zapisane do bazy czy coś ?

Myślisz, że w takim Ogame, żadna akcja typu wybudowanie, update czegoś itd. nie idzie od razu do bazy ?

Problem rozbijasz na mniejsze:

  • logiki biznesowej (jak działa Twoja gra/aplikacja)
  • infrastruktury (jak sprawić, by nie było Single Point of Failure, albo żeby utrata danych była jak najmniejsza, o ile jest akceptowalna - co innego fail dla pojedyńczego klienta, a co innego dla 90%)

Request może być pakowany do kolejki na systemie plików/ "pamięć" może być replikowana na wiele maszyn itd. Są różne wymagania i różne techniki.

0

@Shalom
Czy dobrym pomysłem byłoby tu użycie repozytorium (czy czegoś podobnego), tak, żeby kod domenowy nie musiał wiedzieć czy pod spodem dane faktycznie lecą do bazy, czy są tylko trzymane w pamięci?

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