wtorek, 2 grudnia 2014

fluentbuilder generator 1.1.0 released

W końcu znalazłem czas (czytaj zwolnienie lekarskie) i mogę dopiąć tematy, które już dawno powinny być zrobione. Tym samym wypuszczam wersję 1.1.0 swojego mini softu do generowania fluent builderów, przydatnych do pisania testów w javie.

Można zadać pytanie, po co komu kolejny open source, robiący to samo co już wcześniej zostało rozwiązane - ano nie do końca. Podsumujmy alternatywy, które możemy znaleźć w internecie:

  1. fluent-builders-generator-eclipse-plugin
  2. Builder Generator
  3. pojobuilder
  4. Lombok
  5. fluent-interface-proxy
  6. fluentbuilder (Blue Carat fork)



Jest tego więcej, ale pominąłem niektóre, bo wydają mi się identyczne z pozostałymi. Jeśli nie ma tu jakiejś istotnej alternatywy to proszę o komentarz.

Wychodzimy z założenie, że builderów będziemy używać TYLKO do testów. Dlaczego? Na potrzeby testów chcemy sobie szybko i wygodnie (fluent) przygotować obiekt, będący w pewnym stanie (np. zrealizowane zamówienie), do którego dojście "legalnymi" sposobami wymaga sporej ilości kodu. Puryści mogą się oburzyć - jak to? Po co w takim razie zrobiliśmy maksymalną enkapsulację? Przyznaję - tego typu buildery to hackowanie własnej domeny, ale w "słusznym" celu. Spokojnie, robimy to cały czas, korzystając z np. mocków. Mockiści mogą spytać - to dlaczego nie użyć mocków? Są przypadki, gdzie to po prostu nie ma sensu, np. testy zapytań dao. Moim osobistym kryterium w wyborze builder vs. mock jest czytelność (i zwięzłość zapisu).
Order realizedOrder = anOrder().realized().build();
vs.
Order realizedOrder = mock(Order.class);
given(realizedOrder.isRealized()).willReturn(true);
Wracając do porównania. Jeśli chcemy, żeby nasze buildery nie bazowały na setterach (bo wymusi to rozenkapsulowania naszych obiektów), to opcje nr 1. 2. i 3. odpadają. Skoro settery odpadają, implementacja buildera musi być klasą zagnieżdżoną żeby mieć bezpośredni dostęp do pól. Jeśli chcemy żeby buildery nie zaśmiecały naszego kodu produkcyjnego, to opcja nr 3. odpada. Cytując klasyka, nasz "super junior contractor" widząc, że Order ma OrderBuilder wykorzysta tą klasę gdzieś w kodzie produkcyjnym, pomijając nasze bezpiecznie konstruktory/fabryki. Komentarz "tylko do testów" zostanie nieświadomie zignorowany, a w Lomboku chyba nawet nie będzie możliwości jego dodania. Jeśli nie klasa zagnieżdżona, to w czystej javie zostaje tylko czarna magia w postaci refleksji i dynamicznych proxy, zastosowanych w nr 5-6. Fleunt-interface-proxy - szkoda, że nie natknąłem się na ten projekt zaczynając własny, bo miałbym mniej rzeczy do rozpoznania. Generalnie jego podstawowym minusem jest fakt, że kod buildera trzeba napisać ręcznie. Można za to zobaczyć jak implementować dynamiczne proxy w czystej javie. Ja poszedłem na skróty i wykorzystałem cgliba. Numer 6. to tak de facto fork mojego projektu. Jakkolwiek bardzo miło mi się korespondowało z autorem, nie do końca akceptuje to, co stało się z moim kodem :) A może obsesja na punkcie słowa final, to po prostu domena niemieckich programistów :) Przybyło za to sporo komentarzy i w końcu coś mnie zmotywowało do zrobienia kilku ficzerów.

Podsumowując, nie znalazłem obecnie żadnego projektu, który spełniałby podstawowe wymagania:
  • buildery są automatycznie generowane (również inkrementacyjnie)
  • settery nie są wymagane
  • istnieje możliwość dodawania własnych, specyficznych dla danej domeny, metod budujących
To chyba ten ostatni punkt zaważył na tym, że swojego czasu zdecydowałem, iż trzeba to napisać samemu (z merytoryczną pomocą kilka "mądrych głów").

Jeśli chcesz poczytać więcej na ten temat, to zapraszam tutu i tu.

2 komentarze:

  1. Po dodaniu do projektu fluentBuilder dostaje błąd:

    [ERROR] Failed to execute goal org.bsc.maven:maven-processor-plugin:2.0.5:process (generate-fluentbuilders) on project dioica: Error executing: NullPointerException -> [Help 1]
    org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.bsc.maven:maven-processor-plugin:2.0.5:process (generate-fluentbuilders) on project dioica: Error executing
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:216)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:108)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:76)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:116)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:361)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:155)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:584)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:213)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:157)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
    Caused by: org.apache.maven.plugin.MojoExecutionException: Error executing
    at org.bsc.maven.plugin.processor.AbstractAnnotationProcessorMojo.execute(AbstractAnnotationProcessorMojo.java:204)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:133)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
    ... 19 more
    Caused by: java.lang.NullPointerException
    at org.bsc.maven.plugin.processor.AbstractAnnotationProcessorMojo.executeWithExceptionsHandled(AbstractAnnotationProcessorMojo.java:320)
    at org.bsc.maven.plugin.processor.AbstractAnnotationProcessorMojo.execute(AbstractAnnotationProcessorMojo.java:197)
    ... 21 more

    OdpowiedzUsuń
  2. Obstawiam, że coś jest nie tak skonfigurowane w pom.xml, ponieważ na stacku nie ma jeszcze żadnego pakietu z projektu. Porównaj swoją konfigurację do:
    https://github.com/aludwiko/fluentbuilder/blob/master/pom.xml

    OdpowiedzUsuń