Kotlin i Vavr ?

0

Cześć, zaczynam właśnie pisać swój pierwszy funkcyjny serwer ( @jarekr000000 swoimi filmami zainspirował, wykład na geeconie inspiracje zakończył i wprowadził już tryb wykonawczy). Poszedłem krok dalej i zamiast Javy będzie Kotlin.

Kilka pytań:
1.Duże różnice między WebFluxem a Ktorem ?
2. Warto stosować Vavra w Kotlinie ?
3. Teoretycznie rozumiem idęę architektury nieblokującej, ale jakoś nie mogę dojść do tego, że nawet jak ustawimy sobie serwer na 1 wątek to przecież pod spodem i tak będą chodzić wątki, bo jakoś te taski przecież przetwarzany równolegle. Czy to nie jest przenoszenie problemu w inne miejsce ? Tam też przecież te wątki będą się przełączać.

Zdr.

1

Teoretycznie rozumiem idęę architektury nieblokującej, ale jakoś nie mogę dojść do tego, że nawet jak ustawimy sobie serwer na 1 wątek to przecież pod spodem i tak będą chodzić wątki, bo jakoś te taski przecież przetwarzany równolegle. Czy to nie jest przenoszenie problemu w inne miejsce ? Tam też przecież te wątki będą się przełączać.

JavaScript chodzi na jednym wątku od początku świata i jakoś nie ma z tym problemu. Dodatkowe wątki w JSie (np w Node.jsie) są szczegółem implementacyjnym, kod JSowy nie jest na nich odpalany. Skoro w JSie da się zrobić architekturę nieblokującą na jednym wątku to czemu nie miałoby się dać w innym języku?

1

Ad 1. Duże i małe. Zasada działania podobna, ale ktor ma wszystko bardzo po swojemu i ostro korzysta z ficzerow kotlina. Mi było sie ciezko przestawić.
Ad 2. Zdecydowanie tak. Jest też dodatkowy projekcik ułatwiający konwersje miedzy vavr, a pojemnikami kotlinowymi. Kotlin-vavr czy jakoś tak.
Ad 3.Nie. Formalnie wystarczy jeden wątek. To sedno architektury nonblocking i async io. Kiedyś procesory miały jeden rdzen i ten rdzen starczał na obrobienie mnóstwa rownoleglych zadan. Podobnie jest z non blocking. Jeden watek da rade, a max daje sie tyle wątków co rdzeni fizycznych. Jesli potrzebujesz wiecej to cos poszło nie tak(normalka).

0

@jarekr000000:

Wybrałem Spring Reaktywny. Zamiast JPA chcę użyć JOOQ tylko teraz się zastanawiam. Interfejs repozytorium, który będę implementował in memory i w jooq ma zwracać Mono i Fluxy czy normalne kolekcje z javy ? W sensie w której warstwie używa się tych reactorowych typów ?

0

Generalnie już repo może zwracać Fluxy i tak sięto zwykle robi, ma sens. Problem to raczej fakt, że jdbc, a przez to JOOQ jest blokujące, więc trzeba jakoś opakować dla sensu, zwykle trzeba poświęcić co najmniej jeden wątek na jdbc executora.

0

@jarekr000000: Ok, czyli podobnie jak Ty w Ratpongu zrobić jakiś procesor do zarządzania wątkami jdbc tak ?

I jeszcze sprawa testowania takich reaktywnych rzeczy. No bo muszę przetestować jakoś zawartość z Mono/FLuxa. Po prostu na testach robić to async i wołać blocki ?

0

Tak. Nie znam w tej chwili sensowniejszego wyjścia, choć to zakrawa ns skandal.
Alternatywny przykład springowy jest tu
https://dzone.com/articles/spring-5-webflux-and-jdbc-to-block-or-not-to-block

0

Jak masz cos blokującego to możesz zrobić Mono.fromCallable i tam blokujący kod i zapiąc swojego schedulera, którego sobie sam stworzysz i tam będziesz mieć jakąs pule wątków do ogarniania tego. W testach normalnie walę .block() Spring ma już też dużo wsparcia dla reaktywnych rzeczy, więc jezeli jest driver też reaktywny to springowe repa też mogą zwracac fluxy/mono

0

@jarekr000000:

A co tak naprawdę przemawia, aby używać tego vavra w kotlinie ? Niemutowalne kolekcje tam mamy, do tworzenia funkcji nie potrzebujemy obiektów jak Function czy Supplier, robimy po prostu (Int) -> Int . chodzi o bogatsze api operacji na tym wszystkim ?

2

Przejrzałem na szybko dokumentację Kotlina i tamtejsze niemutowalne kolekcje nie wspierają structural sharing. Są to po prostu widoki tylko do odczytu. Sensowna implementacja niemutowalnych kolekcji umożliwia tworzenie nowej kolekcji na podstawie starej starając się współdzielić jak największą ilość danych ze starą kolekcją. Przykład jest np tutaj - https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2 - tworzenie nowej mapy na podstawie starej (tzn dodawanie jednego elementu do niemutowalnej kolekcji) powinno odbywać się bez przebudowania całej mapy. Polecam przeczytać ten artykuł, a także zwrócić uwagę na to, że widoki tylko do odczytu w Kotlinie nie mają funkcji zwracających nowe kolekcje (typu append, prepend, update, etc) co znacznie zmniejsza wygodę korzystania z nich. Zobacz też na tabelę wydajności operacji w Vavrze: http://www.vavr.io/vavr-docs/#_performance_characteristics Dla przykładu List.prepend zwraca nową listę w czasie stałym, TreeMap.put zwraca nową TreeMapę w czasie logarytmicznym, itd

W skrócie - w Vavr mamy https://en.wikipedia.org/wiki/Persistent_data_structure (wariant: fully persistent) a w Kotlinie widok tylko do odczytu.

0
Wibowit napisał(a):

w Kotlinie nie mają funkcji zwracających nowe kolekcje (typu append, prepend, update, etc) co znacznie zmniejsza wygodę korzystania z nich.

To akurat nie do końca prawda.

val foo = listOf(1)
val bar = foo.plus(2)
val baz = foo.plus(bar)
val qux = baz + 3
val quux = qux + qux

To samo tyczy się minus.

0

@Michał Sikora: Racja, zbyt szybko przeglądałem i przeoczyłem. Jednak te operacje zawsze budują nową kolekcję od zera, a więc np dodanie elementu do TreeMapy ma złożoność obliczeniową O(n lg n) i pamięciową O(n) zamiast złożoności O(lg n) obliczeniowej i pamięciowej w przypadku kolekcji ze structural sharing.

Oprócz zawyżonych złożoności z powodu braku structural sharing mamy niepewność co tak naprawę dostaliśmy - widok tylko do odczytu może oznaczać, że mamy do czynienia z niemutowalną kolekcją, ale równie dobrze ta kolekcja może być mutowalna i co gorsze mutowana gdzieś indziej w trakcie gdy my z niej korzystamy. Dokumentacja Kotlina podaje:

Unlike many languages, Kotlin distinguishes between mutable and immutable collections (lists, sets, maps, etc). Precise control over exactly when collections can be edited is useful for eliminating bugs, and for designing good APIs.

It is important to understand up front the difference between a read-only view of a mutable collection, and an actually immutable collection. Both are easy to create, but the type system doesn't express the difference, so keeping track of that (if it's relevant) is up to you.
(...)
Note that the readOnlyView variable (ed: MutableList zrzutowana na List) points to the same list and changes as the underlying list changes. If the only references that exist to a list are of the read-only variety, we can consider the collection fully immutable.

W Ruście też nie ma structural sharing, ale tam przynajmniej kompilator pilnuje by nie mieszać referencji pozwalających na mutowanie z referencjami tylko do odczytu. Mając referencję tylko do odczytu w Ruście można być pewnym, że kolekcja w danym miejscu będzie zachowywać się jak niemutowalna.

1

Ja nie mówię, że to jest jakieś wydajne. Po prostu jest ;). Zrobione jest tak, jak jest zrobione, bo JetBrainsi chcieli mieć bardzo mocny interop z Javą. Wpasowuje się to w dewizę pragmatyzmu Kotlina. Aczkolwiek włos się jeży na głowie czasem. Nawet nie wiem, który wynik gorszy z dwóch poniższych.

val readonly: List<Int> = List(3) { it }
if (readonly is MutableList) readonly.add(3) // Ups!
println(readonly)
val readonly: List<Int> = listOf(0, 1, 2)
if (readonly is MutableList) readonly.add(3) // Ups!
println(readonly)
0

@jarekr000000: @hcubyc

A jak to jest ze zwracaniem Fluxa z repozytoriów ? W sensie jeśli mam metodę findObjectsByParam(param: String), która mi zwróci listę po paramsie to ma ona zwracać Flux<Object> czy Mono<Collection<Object>> ? I czy ten String param też ma być Mono ?

Bo wlaśnie doszedłem do tego, że jakiś zewnętrzny serwis zwraca mi Mono<String> i mam taką metodę jak findObjectsByParam(param: String): Flux<Objects>. Czyli wywołanie wygląda mniej więcej tak:

getMonoParam().
map { repo.findObjectsByParam(it) }

I zamiast Flux<Object> dostaję Mono<Flux<Object>>

Nie wiem teraz za bardzo, w którym miejscu mam to spłaszczyć.

2

Możesz użyć flatMapMany (ogólnie jeżeli jednego publishera chcesz mapować na drugiego, to powinieneś użyć jakiegoś wariantu flatMap zazwyczaj), ale lepiej mieć na starcie poprawne źródło danych. Czasem to będzie Mono<Collection<T>> czasem Flux<T> - zależy od przypadku użycia i tego jak chcesz procesować dane.

0

No właśnie o to też mi chodziło .. jakie są przypadki używania Mono i Flux. Ja to będę wypluwał na front.

1

Generalnie wyplucie Flux<T> jest bardziej ogólne od Mono<Collection<T>>.
Możesz:

  • wypluć bardzo dużo, a nawet nieskończneie wiele danych (które nie mieszczą się w pamięci),
  • klient może zacząć obrabiać pierwsze rezultaty zanim Ty na serwerze pozbierasz wyniki ( o ile wiesz jak to zrobić),
  • łatwiej Flux<T> komponowac z innymi strumieniami reaktywnymi,
    Pytanie, czy jest Ci cokolwiek z tego potrzebne... bo

wadą jest:

  • większy narzut (cpu, pamięć),
  • większa komplikacja kodu (przeważnie),

Jesli chodzi o input - to w zasadzie nie widzę sensu, żeby mieć argument Mono<T> zamiast po prostu T. (bo tu nawet jak dostaniesz skądś Mono<T> to w map i juz maszT. A kod prostszy,

0

Z fluxem jest też problem gdy chcesz do tej pory korzystałeś ze springowego Page'a, więc jeżeli będziesz mial przypadki użycia na Fluxa to spoko, ale żeby miec Fluxa dla idei to tak średnio bym powiedział

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