czwartek, 7 marca 2019

Odmagicznianie

Patrząc na aplikację na jednym końcu jest framework (tu i ówdzie nazywany przez starych ludzi zrębem) a na drugim assembler, kod maszynowy, elektrony czy inne kwarki w zalezności od tego jak głęboko chcesz wchodzić. Im wyższy poziom tym - w teorii - powinno się szybciej pracować, gdyż ukrywana przed nami jest skomplikowana drobnica, ale - w praktyce - i tak prędzej czy później dostaniesz plaskacza w twarz od warstw poniżej (tutaj można klasycznie zaliczkować do artykułu o "cieknących abstrakcjach" z 1413 roku)

No i miała miejsce taka konkretna sytuacja. Robię warsztaty z nowego API Kotlina na Springu. To API jest fajne bo tam nie ma żadnych adnotacji (dlaczego to jest fajne to jeszcze o tym w naszym artykule będzie) i wykorzystując wiele z bardziej zaawansowanych kotlinowych mechanizmów pozwala zbudować konfigurację beanów,restów itd tylko i wyłącznie przy pomocy czystego kodu.

Warsztaty były z Kotlina. To API springowe dosyć dobrze napisane to myślę sobie - będzie to dobra ilustracja mechanizmów. Jednakże po zanurkowaniu w głąb kodu kilku osobom poleciała para z mózgu i tam przy okazji smoltoków padła taka koncepcja, ze w sumie może lepiej było zostać na poziomie wywołań i nie wchodzić w szczegóły.

Otóż wydaje mi się, że jednak nie byłoby lepiej. I to nie tylko dlatego, że taka przygoda ma to samo w sobie wartość edukacyjną w kontekście nauki Kotlina. Jest jeszcze jeden aspekt całej sprawy...

#10yearsczelendż - JDD 2009

Rok 2009 - czyli w świeci informatyki jakieś dwa wieki temu - był rokiem ciekawym. Wyobraź sobie, że świat jest w czymś czego młodzież IT rzucająca 10K na wejście jeszcze nie zna a co się nazywa "Kryzys Światowy".Java 6 jest szczytem nowoczesności. Youtube ma rozdzielczość 320p a zamiast fejsbuka jest nasza-klasa.

I właśnie wtedy miałem przyjemność uczestniczyć w konferencji JDD 2009 gdzie przyjechał pewien typ od JMS. No i tenże człowiek miał między innymiwykład o tym "jak NIE życ" czyli o antywzorcach w programowaniu. Lub coś takiego, coś w ten deseń. Było kilka ciekawych antywzorców ale nas interesuje ten jeden gdzie było coś o "magi" i pamiętam, że pokazał kawałek kodu z adnotacją @Transactional.


@Transactional
public void metoda(){
..
} 

To był Spring 3.coś i kontekst użycia magi polegał na tym, że ludzie wrzucali te adnotacje i mało kto wgłębiał się jak to działa. No i okazało się, ze w zależności od typu wyjątku jaki poleci - checked czy unchecked - działa to inaczej. Czasem będzie rollback a czasem nie (przynajmniej tak to działało A.D. 2009 )

Separacja i edukacja

Pomocne nam teraz będą dwa kawałki kodu.

Pierwszy przedstawia konfigurację RESTa adnotacjami. I widząc ten kawałek kodu w zasadzie trudno jest stwierdzić co się stanie. Trudno jest stwierdzić kiedy to się stanie.Trudno odpowiedzieć na pytanie czy jakiś wrapper będzie generowany w trakcie kompilacji, a może coś będzie czytać przez refleksję te adnotacje i jakaś bliżej nieokreślona logika pojawi się naokoło naszej klasy?

Dokładne przestudiowanie dokumentacji jest potrzebne - co samo w sobie nie musi być złe - ale to wymaga mocy skupienia aby zadość uczynić warunkowi "dokładne". A jak życie pokazuje dokumentacja może mieć nieścisłości, może mieć braki i dziury. Być może jest jakiś specjalny plugin do IDE, który powiąże adnotacje z miejscem wywołania i jakoś zbadać ten kod - ale raz, że nie wnikałem czy w ogóle coś takiego istnieje - a dwa, że uzależnia nas to od kolejnej rzeczy, kolejnego narzędzia.

  1. @RestController
  2. @RequestMapping("/foos")
  3. class FooController {
  4.  
  5. @Autowired
  6. private IFooService service;
  7.  
  8. @GetMapping
  9. public List<Foo> findAll() {
  10. return service.findAll();
  11. }
  12.  
  13. @GetMapping(value = "/{id}")
  14. public Foo findOne(@PathVariable("id") Long id) {
  15. return RestPreconditions.checkFound( service.findOne( id ));
  16. }

Do tego dochodzi jeszcze jeden ważny aspekt czyli - "kiedy tak naprawdę dowiem się, że coś zrobiłem źle i ile czasu zmarnuje aby rozpocząć naprawę buga". Kompilacja następuje (czasem dużo) wcześniej niż testy/odpalenie aplikacji - także jeśli czas to pieniądz to jest widoczna oszczędność pieniędzy. I znowu mogą pomóc jakieś pluginy do IDE tylko,że wiecie - naprawdę te pluginy to rozwiązanie sztucznego problemu, który sami sobie zrobiliśmy...

Teraz patrz na to:

  1. fun helloRouterFunction(): RouterFunction<ServerResponse> {
  2. //This one uses official kotlin DSL for building Routerfunction and Handlerfunctions
  3. return router {
  4. GET("/hello") { _ ->
  5. //and below is HandlerFunction
  6. ok().body(just("Hello World!"), String::class.java)
  7. }
  8. }
  9. }

Ok, jest to jakaś konstrukcja, której działania muszę się domyśla. Dokumentacja może być a może jej nie być. A Może mam jakieś specyficzne scenariusze, których nie ma w dokumentacyjnych hello worldach? Tutaj mam opcję! To zwykły kod. Można pobrać źródła , ctrl+b i juz patrzę co to za router

  1. fun router(routes: RouterFunctionDsl.() -> Unit) =
  2. RouterFunctionDsl().apply(routes).router()

Jest to prosta linijka kodu - prosta kiedy znasz kotlina, ale po to robię warsztaty - i jak już tego kotlina się umie czytać to to jest bardzo oczywiste co tutaj się dzieje. Jest taki builderek nazwany tutaj DSLem , do którego wpakujemy ustawienia routera. Na końcu jak to w builderach bywa jest wywołanie metody build , która tutaj nazywa się router

I teraz taka zagadka. Dlaczego mogę sobie napisać GET w środku routera ale już poniższe się nie kompiluje?

  1. GET("/hello") { _ ->
  2. //and below is HandlerFunction
  3. // Handler function is explained in IntroSpring2
  4. //and Reactor api in IntroSpring2 and MonoDemo
  5. ok().body(just("Hello World!"), String::class.java)
  6. }
  7. return router {
  8. //for explanation how GET is build take a look at IntroSpring2
  9. }

By odmagicznić sytuację zwyczajnie włażę w implementację tej metody i widzę, ze jest to metoda instancji wcześniej widzianego buildera, która modyfikuje stan tejże instancji - NO RACZEJ ma to sens by wołać ja w kontekście jakiegoś obiektu, EJ HELOŁ!

  1. /**
  2. * Route to the given handler function if the given request predicate applies.
  3. * @see RouterFunctions.route
  4. */
  5. fun GET(pattern: String, f: (ServerRequest) -> Mono<ServerResponse>) {
  6. routes += RouterFunctions.route(RequestPredicates.GET(pattern), HandlerFunction { f(it) })
  7. }

Wędka zamiast adnotacji

No i morał z tego taki, że im to wszystko, całe te mechanizmy są mniej przekombinowane tym naprawdę łatwiej rozkminić co się dzieje bo dokumentacji czasami jest jej jak na lekarstwo. Np. to Nowe API kotlinowe w springu to na jakimś blogu znalazłem opisane a dalej to sam już drążyłem i wynikiem sa materiały na gitbooku :

No i też te całe adnotacje interpretowane w runtime to trochę taki PHP napisany w Javie, macie dwa poziomy języka. To jest złe.

Warsztat na segfault university

Ten temat w formie 4-wymiarowego warsztatu poruszę na Segfault University w gdańsku w następnym tygodniu. Zapisywać można się chyba gdzieś tutaj : http://segfault.events/sites/gdansk2019/speakers/pawel-wlodarski/