Projektowanie "dobrawego" API.

0

Cześć.

Użyłem słowa "dobrawego" bo mam na uwadze, że na jakieś kompromisy zawsze trzeba iść, nie mówię o idealnym API, tylko o takim z którego da się dość dobrze korzystać.
Ponad pół roku temu zostałem przydzielony do nowego projektu (jako frontend developer) który ma powstać na architekturze mikroserwisów. System do przetwarzania bardzo dużych formularzy (1 formularz na kilkanaście/dziesiąt stron). Na początku się ucieszyłem, bo do tej pory pisałem aplikację wykorzystywaną wewnątrz firmy - więc w końcu jakiś przeskok. Frontend jakoś sobie zrobiłem, nie było makiet więc nie wyglądało to jakoś rewelacyjnie - ale w miarę działało. Przyszło do podpięcia backendu i frontendu. Wtedy już miałem pewne wątpliwości. Zdziwiłem się bo zapis Formularza był rozbity na kilkanaście/dziesiąt requestów - dowiedziałem się, że przyjęli takie podejście żeby wyeliminować konieczność rollbacków w sytuacji gdy z jakiegoś powodu jeden mikroseriws rzuciłby błędem. Rozdzielili (nie mając przyklepanego jak ma to docelowo wyglądać) dane przechowywane w różnych mikroserwisach, tak że dane na jednej stronie pochodzą z jednego mikroserwisu. Wyciągnąć całego obiektu żeby zrobić poprawnie nawigację i mechanizm generowania przekierowań do pól na formularzu nie mogę (bo takiego endpointu nie ma). O ile zapis jestem jakoś wstanie zrozumieć, tak to kompletnie jest dla mnie głupotą.

Mam dopiero półtora roku doświadczenia i może źle to wszystko pojmuję, ale czy odpowiedzialność backendu i frontendu powinna być oddzielona jak najbardziej się da? W takim sensie, że jeżeli funkcją systemu (od strony biznesowej) jest przetworzenie formularza, to API powinno być tak zaprojektowane, żeby udostępniało możliwość wyciągnięcia Formularza oraz jego zapisu w całości?

Przy wyciąganiu formularza to nawet nie wiem jakie argumenty dostałem, bo niektóre były tak mało sensowne, że nawet ich nie zapamiętałem. Jedyny argument w miarę sensowny był taki, że szybciej jest wysłać kilkadziesiąt małych DTO niż jedno wielkie. I poniekąd się z tym zgodzę. Jeżeli backend miałby coś takiego zrobić to standardowe podejście ze Sprigna by nie miało prawa bytu tutaj bo rzeczywiście mogłyby polecieć jakieś timeouty, z kolei jeżeli frontend miałby to wszystko scalić to tutaj jest się ograniczonym przez przeglądarkę - z tego co czytałem, np chrome może równolegle wysłać 6 requestów dla jednego origina/domeny (nie pamiętam już), potem je pewnie kolejkuje. Z kolei nic mi nie wiadomo o tym, żeby backend takie ograniczenia posiadał. O ile dobrze zrozumiałem, to Webflux oferuje nieblokujące requesty (podobnie jak np. express.js) więc gdyby wystawić jeden główny mikroserwis do komunikacji z backendem i frontendem - nie byłoby to aż tak źle ( https://medium.com/@filia.aleks/microservice-performance-battle-spring-mvc-vs-webflux-80d39fd81bf0 nie wiem ile ten benchmark jest warty, ale w niektórych sytuacjach WebFlux jest nawet 4x wydajniejszy). Jak wtedy backend sobie przechowuje te dane - frontend ma wywalone na to. Podobnie z zapisem (tylko tutaj jakoś jestem wstanie to zrozumieć).

Jeżeli formularz jest przechowywany na 3+ mikroserwisach: A,B,C gdzie C jest agregatem A i B, oraz A i B nie zależą bezpośrednio od siebie, to czy poruszając się wewnątrz kontekstu C, A nie wpływa pośrednio na B? Przecież jeżeli A się wywali, to co z tego że B działa jak A wpływa na to, że nie można korzystać z funkcji udostępnianego przez C? Więc nawet jak wyciągnę B, to nic mi to nie daje bo Formularz przy zapisie się wysypie. Mam wrażenie, że następuje tutaj próba rozwiązania problemu wydajnościowego bazy danych przez zastosowanie mikroserwisów zamiast NoSQL (niestety NoSQL nie można użyć w tym projekcie).

3

Brzmi bardzo dziwnie. Tzn mikroserwisy spoko, ale częściej jednak robi się jakiś "agregator" w backendzie który to koordynuje, tak ze frontend zna tylko jeden URL z którym się komunikuje, a reszta komunikacji idzie server-2-server na poziomie backendu. To dodatkowo ułatwia zarządzanie security bo 90% serwisów będzie w ogóle niewidoczna z zewnątrz.

0

Znaczy nie do końca o to mi chodziło. Bo rzeczywiście ma powstać jakieś proxy, które będzie odpowiedzialne za komunikację z pozostałymi mikroserwisami, tylko ja bardziej mówię o tym, czy aż taki podział w API ma sens? W sensie, tam jest rzeczywiście od groma stron i obiekt może się okazać bardzo duży, nie mniej jeżeli dana funkcja systemu polega na zapisywaniu Formularza, to zrobienie zapisu formularza w więcej niż 1 requeście może spowodować jakieś problemy (typu co jeśli pola na jednej formatce będą pochodzić z kilku mikroserwisów) oraz ujawnianie w jakimś stopniu architektury backendu. Tylko też jest taka sytuacja jak mówię, już są problemy związane z tym że nie mam dostępu do całego formularza (np. przy walidacji i generowaniu przekierowań do poszczególnych stron formularza na podstawie błędów). Backendowcy założyli, że to co na mikroserwisie odpowiada temu co na formatce - i zapis dotyczy stricte tej formatki. Może się okazać, że jednak będzie potrzeba żeby na stronie wyświetlić i zapisać dane z klilku mikroserwisów.

Edit: Sytuacja jest taka, że formularz całościowo składa się np. z 20 fizycznych stron (kartek papierowych), zostaje rozbity na 60 stron internetowych i teraz każdą z tych stron osobno się zapisuje przy wyjściu z strony. Dodatkowo istnieją strony, które są zapisywane w kilku requestach, w zależności od tego jakie dane na nich są.

0

Przy wyciąganiu formularza to nawet nie wiem jakie argumenty dostałem, bo niektóre były tak mało sensowne, że nawet ich nie zapamiętałem. Jedyny argument w miarę sensowny był taki, że szybciej jest wysłać kilkadziesiąt małych DTO niż jedno wielkie. I poniekąd się z tym zgodzę. Jeżeli backend miałby coś takiego zrobić to standardowe podejście ze Sprigna by nie miało prawa bytu tutaj bo rzeczywiście mogłyby polecieć jakieś timeouty, z kolei jeżeli frontend miałby to wszystko scalić to tutaj jest się ograniczonym przez przeglądarkę - z tego co czytałem,

tak sobie czytam - nono, nawet coś tam brzmi to sensownie, może rzeczywiście bóg wie co tam jest wysyłane i spring umrze

a później

np chrome może równolegle wysłać 6 requestów dla jednego origina/domeny (nie pamiętam już), potem je pewnie kolejkuje

wtf?

a czemu by nie wysyłać partów co każdy zakończony etap, a nie dopiero na końcu wszystko na raz?

nie wiem, może czegoś nie łapie :P

0

Request może polecieć z tego powodu, że cholera wie jak oni to zrobili tam, jak to będzie wyglądać i jak będzie wyglądać komunikacja między mikroserwisami.
Nie istnieje coś takiego jak "etap" (tzn nie w tym sensie o którym piszesz). Są zależności między stronami. Tak, jest to wysyłane zaraz po wyjściu z formularza. Tylko problem jest teraz taki, że zapis strony formularza może być w kilku requestach. Dodatkowo, trzeba wyciągnąć dane dla następnej strony (która w czasami może spowodować wysłanie kolejnych requestów - np po słowniki). I czemu mi się to nie podoba? Bo teraz jakakolwiek rozbieżność między tym, co zostało założone a tym co ma być, będzie skutkowało albo szyciem na miare frontendu (a przy okazji wrzucaniu spinnera na czas trwania requestów) albo zmianą frontu i backendu. I teraz albo dostaje po dupie UX, albo trzeba zmieniać zarówno frontend jak i backend.

Zapis - tak jak mówiłem, może jestem wstanie się zgodzić żeby to leciało tak jak oni chcą: bezpośrednio do mikroserwisów, w takich requestach jakich chcą, chociaż tutaj też widzę problem taki, że wiążesz ze sobą frontend i backend. Zmiana miejsc pól na formatkach z automatu wpływa na to, że już musisz wysłać zupełnie inne requesty. W pryzpadku gdy na początku przyjmiesz założenie, że na stronie A i B będą niezależne od siebie dane i zrobisz 2 endpointy do zapisu tych danych, a potem się okaże że trzeba będzie wymieszać te dane na tych stronach - z automatu musisz zmieniać API. Natomiast pobranie - kompletnie nie widzę tego, co stoi na przeszkodzie żeby wyciągnąć cały Formularz jednym requestem?

Edit: Jak teraz myślę, to źle zrozumiałem agregator o którym @Shalom mówisz, proponowałem właśnie coś takiego co wychodzi na to, że będzie zapis formularza w jednym requeście (od strony frontendu) - od czego backendowcy chcą uciec, żeby nie było potrzeby rollbackowania w przypadku gdy coś się sypnie.

0

@Aisekai: ale co miałoby się sypnąć?

0

Jeśli chodzi o walidację danych - to takowa rzecz nie ma jak się sypnąć, bo reguły walidacji są na backendzie więc z automatu serwer musi przyjąć wszystko co ma poprawny format danych a w przypadku słowników - raczej sprawdzany jest tylko czy słownik może się zawierać jako możliwy wybór dla danego pola. Sypnąć się może jedynie coś z powodu komunikacji między mikroserwisami.

Odnośnie komunikacji asynchronicznej między mikroserwisami - nie poruszam tego tutaj, bo też była dyskusja o tym czy tam komunikacja powinna iść synchronicznie czy asynchronicznie, według mnie - synchronicznie. Natomiast pytam tylko stricte o API, bo jako frontendowiec mam gdzieś jak wygląda backend (a przynajmniej powinienem mieć to gdzeiś). Jedyne co mnie interesuje to API, bo tym się z nim komunikuję.

1

a może wy potrzebujecie sagi?

0

Rzeczywiście było coś mówione o Sadze ale jako zastosowanie w innej części systemu. O ile mi wiadomo, ta część związana z formularzem pozostanie tak jak mówię, że komunikacja będzie bezpośrednio (lub przez proxy) z danym mikroserwisem, nie będzie możliwości zapisu całego formularza w jednym requeście jak i możliwości jego pobrania (niby ze względów wydajnościowych).

0

z czego wynika ten "narzut wydajnościowy" w tym formularzu?

0

Prawdopodobnie - z nieznajomości WebFluxa i posiadaniu wiedzy o tym, że requesty w Springu są blokujące. Nie wiem, innego powodu nie widzę.

2

Mnie się wydaje, że tu po prostu nikt nie przemyślał UX, więc została dorobione GUI a la desktop z lat 90 (im większy formularz, tym mądrzej wszystko wygląda), a do niego dorabiany jest backend, na zasadzie każdy robi mikroserwisy, więc my też zróbmy.

0

Formularz akurat jest odwzorowaniem innego fizycznego formularza, więc musi być duży. Z kolei, też odniosłem takie wrażenie że API nie zostało przemyślane i zrobione na zasadzie: "byle by było" i pójście w mikroserwisy było mocno ukierunkowane trendem a nie jakoś mocno przemyślane. Wiecej mikroserwisy tutaj przeszkadzają niż pomagają, a problem nie wziął się z architektury pierwszej wersji systemu (bo zapomniałem powiedzieć że to jest przepisanie monolitu na mikroserwisy) tylko konieczności posiadania sqlowej bazy danych.

4

Formularz akurat jest odwzorowaniem innego fizycznego formularza, więc musi być duży.

formularz.png

konieczności posiadania sqlowej bazy danych

xD na pewno jest taka konieczność :D Tez mam w pracy takich agentów którzy myśleli ze koniecznie trzeba wszędzie mieć bazę i w ogóle najlepiej cały model domenowy zmapować do bazy i pierwszy draft pewnego mikroserwisu miał ~40 tabel. Aktualna wersja ma ich 4 i spełnia dokładnie te same wymagania.

1

@Aisekai:

pójście w mikroserwisy było mocno ukierunkowane trendem a nie jakoś mocno przemyślane

to podrzuć im to MonolithFirst

As I hear stories about teams using a microservices architecture, I've noticed a common pattern.

>* Almost all the successful microservice stories have started with a monolith that got too big and was broken up
>* Almost all the cases where I've heard of a system that was built as a microservice system from scratch, it has ended up in serious trouble.
0

Mówiąc o konieczności korzystania z sql, miałem na myśli to, że klient ma licencję na jedną z baz danych i chce się jej trzymać. Nie wiem czy dałoby radę usunąć i zmniejszyć liczbę tabel (pewnie tak) bo nie znam domeny i funkcji systemu. Nie mniej uważam, że lepiej siadłby tutaj nosql + mniej mikroserwisow. Nie chcę mówić tutaj o bazie danych, bo według mnie nie powinna mieć ona znaczenia przy projektowaniu API. Każdy z mikroserwisow ma tam killa tabel (5-6 może). Nie wiem ile docelowo ich będzie. W starym systemie jest ich bardzo dużo.

Poniekąd był już Monolit, bo to jest przepisanie starego systemu na mikroserwisy. Tylko się zastanawiam nad tym, czy API nie powinno być zaprojektowane tak, żeby zapisać cały ten Formularz (mimo że to duży obiekt) jednym requestem? Tak samo wyciąganie go?

0

Nie czuję się nijak eskpertem od webu, ale z UX trochę do czynienia miałem to pozwolę sobie na zabranie głosu:
@Aisekai: sam napisałeś, że są zależności pomiędzy polami formularza. Nie wiem co to jest, ale jeśli coś na modłę, powiedzmy, PITu: czyli "jeśli zaznaczyłeś pkt. 4a to wypełnij 30c", to IMHO tak jak @somekind i @Shalom sugerują to może być problem w UX. Po stronie podziału na współzależne strony np. zamiast robienia go "dużym". Pytanie na ile masz na to wpływ.

0

Niestety wpływu na to nie ma ze względu na to, że one są odwzorowaniem 1:1 fizycznego formularza, funkcja systemu ogólnie polega na tym, żeby przetwarzać fizyczne dokumenty. Jest to mniej więcej coś o czym mówisz, tylko tutaj "głównym" źródłem danych jest fizyczny formularz.

2

Ten podział na mikrousługi jest trochę dziwny, bo jak zmieni się UI, np. wypadnie albo zostanie dodany nowy krok, to rozumiem, że pociąga to za sobą zmianę architektury pod spodem? Co się właśnie stanie jak jakaś strona się nie zapisze? Jeśli jest tutaj pewna krokowość, to gdzie będzie zapisywany stan? Trochę wyglada na to, ze ktoś tam nie odrobił lekcji, nie mówiąc już o tym ze powstaje kod, a nie ma koncepcji na zintegrowanie tego.

Jedna sprawa to UI, a druga to dobry podział domenowy. To nie zawsze jest tożsame - jak sobie zobaczysz np. na Allegro, to na stronie oferty masz wiele danych z różnych kontekstów - niby jedna strona, a pod spodem wiele mikrousług.

Jeżeli jesteś frontendowcem i dostałeś zadanie ogarnięcia UI do jakichś wniosków, to nie wiem czy rozwiniesz skrzydła - zakładam, ze najtaniej i zarazem w sposób przejrzysty jest to opazdzierzyc jakaś tabelka albo równie prymitywnymi mechanizmami (zależy czy ma to działać na różnych rozdzialkach itd). To nie maja być wodotryski, tylko... pola do wypełnienia :) robiłem kiedyś coś podobnego w Angularze - trzeba było na UI odwzorować 1-1 tabelki w bazie :P

Kwestia bazy danych pod spodem - trochę wtórne, jak jest ten Oracle to trudno - o wiele ważniejsze jest dobre zaprojektowanie modelu. W celu rozdzielenia tych modeli usługi mogą mieć różne schematy z ograniczeniem dostępów. Istotna sprawa, żeby nie grzebały sobie nawzajem po danych.

0

Według backendowców - jeżeli UI się zmieni, to będzie trzeba odpalić spinner na fulscreena i prawdopodobnie zablokować możliwość uzupełniania formularza. Nie znalazłem jeszcze na szybko jakiegoś "zgrabnego" rozwiązania pozwalającego na śledzenie które strony formularza byłyby od siebie zależne. Jak stan jest zapisywany - to też jest według mnie dramat bo jak tak sobie teraz myślę to nie ma on sensu.

Masz rację z tym, że nie rozwinę skrzydeł, jednakże problem jest taki, że zostałem poniekąd wrzucony do frontendu. Jako, że doświadczenia jeszcze w ogóle nie miałem to wolałem zostać (żeby w przyszłości było łatwiej pracę znaleźć). Wybór był między Javą i klepanie w JSF albo JS + Angular. Mając na uwadze jak wygląda projekt w JSF - trzymałem się rękami i nogami JS + Angulara. I o ile JS + Angulara lubię, tak CSS są moją najsłabszą stroną. Jedyne czego przy tym Formularzu się nauczyłem to poznałem dość dobrze Angulara + Ngrx + RxJs + JS (na tyle dobrze, że na ostatnich 2 rozmowach o pracę odpowiedziałem na wszystkie pytania).

Zmieniając trochę temat. Wiedząc, że CSS nie są moją mocną stroną (prostą stronę byłbym wstanie napisać) i mając inną ofertę pracy (również front, taka sama stawka po negocjacji przez PM mojego wynagrodzenia) i wiedząc że w obecnej firmie panuje bardzo fajna atmosfera, zmienilibyście pracę?

1

czyli fizyczny formularz, może jakiś urzędowy, taki że może się zmienić od najbliższego miesiąca i będfzie wypełniania przez kwartał równolegle starego i nowego w zależności od daty dokumentu?

1

Więc ja robiłam coś takiego właśnie od strony backendu i do głowy by mi przyszło żeby wysyłać na front w kilku kawałkach. Były właśnie takie sytuacje że urzędowy formularz wypełniało się na przełomie roku zarówno za grudzień w starej wersji jak i za styczeń w nowej , wiec nagłówek formularza miał pola (niewidoczne dla użytkownika końcowego) typ i podtyp, na podstawie których wybierało się reguły przetwarzania. Każda wersja miała własne pliki w js o ile pamiętam, nie było to może elegancko ale praktycznie. oczywiście nowy js się kopiowało z poprzedniego i potem wprowadzało zmiany (chyba, jakl mówię ja tego nie pisałam, ale skoro błędy i literówki zostawały) . Jakieś akcje typu "przepisz wartości z poprzedniego miesiąca" to na serwerze były, ale też przycisk w przeglądarce "pobierz sumy z poprzedniego miesiąca" gdyby użytkownik se nagle przypomniał ze warto, albo już je edytował i namieszał. I zapis oczywiscie z przesłaniem całości jednocześnie

0

Dużo tu treści i nie wiem czy ktoś już o tym mówił i czy dobrze rozumiem ale OP ma problem z możliwościami frontendu w zakresie zapytań do wielu mikroserwisów spowodowanym złym API. Normalnie takie rzeczy spina się jak na załączonym obrazku gdzie w najniższej warstwie SYSTEM każdy moduł pobiera dane ze swojego poletka, a w najwyższej USER EXPERIENCE zwracane są właściwe dane w jednej odpowiedzi. Środkowa może transformować/łączyć dane tak by pasowały pod to co się wyświetla w gui
https://res.cloudinary.com/practicaldev/image/fetch/s--SD5bbxdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ahy28iwy02acszc9c6e2.PNG

0

@Miang o ile dobrze zrozumiałem, to tutaj jest dalej ten sam problem. Jeżeli to jest jeden wielki formularz, gdzie dane masz wyświetlone na n stronach, jak podzielić to tak, żeby móc przesłać to w kilku requestach jednocześnie mając na uwadze, że mogą zaistnieć sytuacje w których na jednej formatce trzeba będzie wyświetlić dane z kilku mikroserwisów? Według mnie - nie da się i taka sytuacja będzie wymuszać wysłanie 2 requestów, w przyszłości może się to skomplikować. Dodatkowo też jakoś trzeba będzie kombinować z tym, że backend przy próbie zapisu wymaga pełnego obiektu, a nie wystawia takiego endpointa żeby ten obiekt pobrać. No chyba, że masz na myśli, że to backend powinien skleić cały formularz i wtedy powyższy schemat reprezentuje taki przypadek.

@Bonanzaa Dobrze zrozumiałem z tego schematu, że warstwa: "User Experience API" przedstawia tak naprawdę funkcje systemu z punktu widzenia biznesowego? Takie jak zapis formularza, logowanie, dodanie czegoś do ulubionych etc.? I powinno się zwrócić cały formularz w jednym requescie mając na uwadze, że pomimo tego że jest on wyświetlany na wielu stronach, to wszystkie te strony tak naprawdę dotyczą jednej funkcji?

1

Dobrze zrozumiałem z tego schematu, że warstwa: "User Experience API" przedstawia tak naprawdę funkcje systemu z punktu widzenia biznesowego?

@Aisekai: User Experience API zwraca dokładnie to czego oczekuje GUI, czyli bierze pod uwagę potrzeby gui. IMO potrzeby biznesowe nie muszą być tożsame z tym co wyświetla się w GUI, więc nie jest to API biznesowe tylko takie na potrzeby GUI. Prędzej Process layer nadaje się na warstwę biznesową, bo z zewnętrznych serwisów tworzy koncepty adekwatne dla danej aplikacji

1

Czy argument o wydajności przesyłania formularza został sprawdzony? (ile taki formularz może zajmować? 5KB tekstu?)
"wyeliminować konieczność rollbacków w sytuacji gdy z jakiegoś powodu jeden mikroseriws rzuciłby błędem" -- no właśnie jak zrobić rollback, jak jest kilkanaście osobnych/ rozproszonych requestów/ transakcji?
"Jedyny argument w miarę sensowny był taki, że szybciej jest wysłać kilkadziesiąt małych DTO niż jedno wielkie" -- skąd taki pomysł? Czy zostało to sprawdzone? (wygląda jak premature optimization) Pewnie dzielą 5KB tekstu na 10 kawałków po 500B -- więcej idzie pewnie na komunikację sieciową (nie mówiąc o opóźnieniach).

0

Nie, nie został sprawdzony. Myślę, że chodziło o sytuację w której taki formularz "sklejany" przez backend w sposób synchroniczny więcej czasu by zajął niż wysłanie go w kilku requestach (i w kilku częściach) wtedy gdy frontend będzie go potrzebował. I o ile zgadzam się, że dane powinny być wyciągane wtedy gdy są one rzeczywiście potrzebne, o tyle uważam że w sytuacji gdy istnieją powiązania między różnymi stronami formularza oraz z punktu widzenia funkcji systemu (od strony końcowego użytkownika) zapisuje się cały formularz, w niektórych sytuacjach będą potrzebne wszystkie dane z formularza na frontendzie.

0

dalej nie rozumiem... Pod jakim względem więcej czasu? Co zajmuje więcej czasu: wysłanie 5KB jednym POST-em, czy 5 * 1KB? I co jest fajniejsze, zapisywanie całego rekordu na raz, dopiero gdy user dojdzie do samego końca wizarda, czy sprzątanie niedokończonych formularzy? (zwłaszcza fajowe jest to w bazach typu dynamoDB, gdzie fullscan jest bardzo drogą ($$$) i czasochłonną (provisioned throughput) operacją)

0

Zakładając sytuację w której użytkownik będzie potrzebował np. 5 pierwszych stron formularza (chociaż taka sytuacja nie istnieje, bo stworzenie przekierowań na podstawie błędu po stronie frontu będzie wymagać większość danych), w teorii szybciej będzie wyciągnąć te 5 stron pojedynczo, dobijając się bezpośrednio do mikroserwisu. Jeżelibyśmy przechowywali cały formularz w 10 mikroserwisach, to chcąc skleić cały formularz masz minimum 10 requestów, domniemuję, że wyciągaliby to synchronicznie. I w tej sytuacji, nawet jak powstanie agregator/fasada (jak zwał tak zwał), to mamy 10*czas komunikacji między mikroserwisem a fasadą + response fasady do frontu.

Tylko jeżeli by to zrównoleglić i dołożyć mechanizmy nieblokujące to czas response będzie max(czasy_wszystkich_response) + response z fasady. Ale tak jak mówię, to są tylko moje domniemania że o to im chodziło.

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