czwartek, 26 września 2013

Fluent builder do generowania fluent builderów cz. 3

Opisany w części 2 generator buidlerów został gruntownie zrefaktoryzowany i ewoluował o poziom wyżej, jego użycie staje się jeszcze łatwiejsze. Korzystając z Annotation Processing Tool, mechanizm generowania klas został przeniesiony do procesora adnotacji, którego możemy na różne sposoby podłączyć do projektu.

Nie nudząc technicznymi szczegółami przejdźmy do przykładu. Klasę np. Order oznaczamy adnotacją @GenerateBuilder, która informuje procesor, że dla niej ma zostać wygenerowany builder:
@GenerateBuilder
public class Order {
 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;

 public boolean isRealized() {
  return realized;
 }
 public void orderRealized() {
  // very complex implementation
  realized = true;
 }
}
Żeby w jakiś sposób automatycznie aktualizować istniejące buildery o nowe pola, które doszły w klasie Order (lub odpowiednio usuwać metody inicjujące nieistniejące pola), builder został rozbity na 2 klasy. Jedna z nich jest aktualizowana zawsze przy uruchomieniu procesora, druga generowana tylko raz, dzięki czemu możemy do niej dodawać własne metody budujące. Dla klasy Order powstaną następujące klasy:
public abstract class AbstractOrderBuilder<B> extends AbstractBuilder<Order, B> {
 public abstract B withItems(List<OrderItem> items);
 public abstract B withCreateDate(Date createDate);
 public abstract B withRealized(boolean realized);
 public B withItems(OrderItem... items){
  return withItems(new ArrayList<OrderItem>(Arrays.asList(items)));
 }
}
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder anOrder(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Samo użycie może wyglądać następująco:
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().withRealized(true).build();
 // then
 assertTrue(order.isRealized());
}
Klasę OrderBuilder można rozszerzać, wywołując rzeczywiste metody domenowe, np:
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
 public OrderBuilder realized() {
  Order order = targetObject();
  // invoking real domain method
  order.orderRealized();
  // other methods
  return builder();
 }
}
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().realized().build();
 // then
 assertTrue(order.isRealized());
}
Do podłączenia procesora do projektu może zostać wykorzystany ant, maven, eclipse. Jest również możliwość użycia generatora w starym stylu, czyli wygenerowania ciała klasy buildera do konsoli.

Procesor może również poszukiwać klas z adnotacjami JPA (@Entity, @Ebeddable, @MappedSuperclass).

Do poznania szczegółów odsyłam do wiki projektu. Zachęcam do forkowania i dzielenia się uwagami.

sobota, 25 maja 2013

Migracja z CVS na Git

Dla tych wszystkich, których nurtują pytania: czy warto, czy to się w ogóle uda, czy Git nie będzie wolniejszy - krótka relacja z migracji dość sporego projektu.

Migracja.

Sprzęt: RedHat, D2 Intel(R) Xeon(R) CPU 5160 @ 3.00GHz,  4 G ramu.
  • oryginalnie projekt na CVS ważył 7 G. Teoretycznie można go było odchudzić do ok. 5-6, aczkolwiek warto było sprawdzić jak będzie wyglądała praca z Gitem na takiej kobyle;
  • do migracji zostało użyte narzędzie Tigrisa: http://cvs2svn.tigris.org/cvs2git.html
    • standardowe ustawienia + 2 rodzaje kodowań plików (wyszło na to, że pliki są inaczej kodowane niż np. same wiadomości commitów, na szczęście można podać wiele kodowań i skrypt dopasowuje aż trafi na odpowiednie);
  • migracja wymagała od 50 do 60 G (tak GIGA) wolnego miejsca na dysku;
  • czas trwania wyniósł ok. 2,5 dnia (tak dnia);
  • rezultatem było serwerowe repo gita, które ważyło 800 M, a lokalne (takie do pracy) repo ok 1.1 G.

Praca z gitem.

Sprzęt: Win Xp, 4 rdzenie, 4 G ramu, dysk SSD.
  • utworzenie nowej gałęzi, commit kilku plików, wysłanie commita do zdalnego repo, fetch zmian - wszystkie te operacje zajmowały maks 3 sekund;
  • przełączanie się pomiędzy gałęziami nowo utworzonymi przez gita - również 3 sekundy;
  • przełączanie się pomiędzy gałęziami utworzonymi przez skrypt migrujący - ok. 30 sekund. Tak "długo" głównie przez to, że wszystkie pliki musiały zostać odświeżone;
  • clone z repo gita: 15 minut. Pewnie szybkość sieci miała tu największy wpływ. Mogłoby być trochę szybciej, ale jest to jednorazowa operacja.

Osobiste wrażenia.

Przyznam szczerze, że po tym jak plik dump zaczął ważyć 50G, skrypt chodził już 1,5 dnia i właściwie nie wiadomo co robił (obciążenie procesora minimalnie, na dysku nie pojawiały się żadne zmiany), to nie wierzyłem, że cała operacja zakończy się powodzeniem. Na szczęście warto było cierpliwie czekać i dzięki temu wiem, że na Gita nie ma mocnych:) Ogólnie zysk całego przedsięwzięcia: przełączenie miedzy gałęziami spadło z 40 minut, do maks 30 sekund.

sobota, 5 stycznia 2013

JUnit rule i mockowanie new Date() cz.2

Chciałbym rozwinąć trochę temat poruszony w 1 części dotyczący mockowania new Date(). Często w testach tworzonych jest po kilka obiektów, dla których oczekujemy, że będą miały np. różne daty utworzenia (pole prywatne w klasie). Nie mając nad tym władzy zdajemy się na JVM i raz te daty faktycznie będą się od siebie różniły, a raz nie. Bazując na poprzednich klasach, test z użyciem Clock'a mógłby wyglądać tak:
@Rule
public Clock clock = Clock.standard();
@Test
public void should() {

 // given
 DateTime firstDate = new DateTime();
 DateTime secondDate = firstDate.plusDays(1);

 clock.setFixedTime(firstDate);
 Order firstOrder = new Order();

 clock.setFixedTime(secondDate);
 Order secondOrder = new Order();

 // when
 // then
}
W rezultacie mamy 2 zamówienia z różnymi datami ich utworzenia. Jeśli nie zależny nam na pełnej kontroli tych dat, to można to zrobić trochę bardziej fancy, np.
clock.afterDay();
Order secondOrder = new Order();
Można również wykorzystać MillisProvider'a i zaimplementować go tak aby każde kolejne wywołanie zwracało "datę" (tj. longa) przesuniętą względem poprzedniej np. o 1 milisekundę. Symulujemy tym samym upływ czasu i mamy pewności, że wywołanie new DateTime() zawsze zwróci nam późniejsza datę.
public class Clock extends ExternalResource {

 private static DateTime currentTime;
 private MillisProvider millisProvider;


 public static final MillisProvider ONE_MILLIS_INTERVAL = new MillisProvider() {

  @Override
  public long getMillis() {
   currentTime = currentTime.plusMillis(1);
   return currentTime.getMillis();
  }
 };

 private Clock() {}

 public Clock(MillisProvider millisProvider) {
  this.millisProvider = millisProvider;
 }

 public static Clock standard() {
  return new Clock();
 }

 public static Clock alwaysNewTime() {
  return new Clock(ONE_MILLIS_INTERVAL);
 }

 @Override
 protected void before() throws Throwable {
  currentTime = new DateTime();
  setProviderIfExists();
 }

 private void setProviderIfExists() {
  if (millisProvider != null) {
   DateTimeUtils.setCurrentMillisProvider(millisProvider);
  }
 }

 @Override
 protected void after() {
  DateTimeUtils.setCurrentMillisSystem();
 }

 /**
  * set date and stop the clock
  */
 public void setFixedTime(Date date) {
  currentTime = new DateTime(date);
  DateTimeUtils.setCurrentMillisFixed(currentTime.getMillis());
 }

 /**
  * set date and stop the clock
  */
 public void setFixedTime(DateTime dateTime) {
  setFixedTime(dateTime.toDate());
 }

 public DateTime getCurrentDateTime() {
  return currentTime;
 }

 public Date getCurrentDate() {
  return currentTime.toDate();
 }
}
I sam test:
@Rule
public Clock clock = Clock.alwaysNewTime();
@Test
public void should() {

 // given
 Order firstOrder = new Order();
 Order secondOrder = new Order();
 // when
 // then
 assertThat(firstOrder.getCreateDate().before(secondOrder.getCreateDate()).isTrue()
}
Kiedy to się może przydać? Na pewno jeśli gdzieś w teście zamówienia lądują w HashMapie, której kluczem jest data utworzenia zamówienia.

JUnit rule i mockowanie new Date() cz.1

Mam wrażenie, że temat został już dawno rozpracowany, ale niektórzy nadal zapominają, że pisząc taki test:
@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 //given
 Order order = new Order();

 //when
 Date createDate = order.getCreateDate();

 //then
 assertThat(createDate).isEqualTo(new Date());
}
dla takiej klasy:
public class Order {
 private Date createDate;

 public Order() {
  this.createDate = new Date();
 }
 public Date getCreateDate() {
  return createDate;
 }
}
będzie on bardzo niedeterministyczny i raz zadziała a raz nie. Dotychczas, w celu pozbycie się problemu, zamiast new Date(), wywoływałem statyczną metodę z JodaTime (this.createDate = DateTime.now().toDate();), którą następnie mockowałem za pomocą PowerMockito. I test mógł wyglądać tak:
@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 //given
 DateTime currentDate = new DateTime();
 PowerMockito.mockStatic(DateTime.class);
 PowerMockito.when(DateTime.now()).thenReturn(currentDate);
 Order order = new Order();

 //when
 Date createDate = order.getCreateDate();

 //then
 assertThat(createDate).isEqualTo(currentDate.toDate());
}
Minusem takiego podejścia jest fakt, że rezerwujemy sobie runnera na PowerMocka i już innego nie będziemy mogli użyć, np. dla testów w kontekście springa. Żeby nie wiało nudą, zainspirowany ostatnim szkoleniem TDD z Rafełem Jamrózem, proponuje inne podejście, a konkretnie wykorzystanie junitowych @Rule. Mechanizm ten jest alternatywą dla @Before i @After, czyli poprzez implementację odpowiedniego interfejsu pozwala na zrobienie czegoś przed i po metodzie testowej. Dlaczego jest to lepsze od @Before i @After - ponieważ raz zaimplementowane, może być używane potem w wielu testach bez dziedziczenia i bez copy-paste. Korzystając z JodaTime mamy do dyspozycji utilsa do sterowania źródłem aktualnego czasu. Implementacja klasy sterującej czasem w testach może wyglądać następująco:
public class Clock extends ExternalResource {

 private static DateTime currentTime;


 private Clock() {}

 public static Clock standard() {
  return new Clock();
 }

 @Override
 protected void before() throws Throwable {
  currentTime = new DateTime();
 }

 @Override
 protected void after() {
  DateTimeUtils.setCurrentMillisSystem();
 }

 /**
  * set date and stop the clock
  */
 public void setFixedTime(Date date) {
  currentTime = new DateTime(date);
  DateTimeUtils.setCurrentMillisFixed(currentTime.getMillis());
 }

 public DateTime getCurrentDateTime() {
  return currentTime;
 }

 public Date getCurrentDate() {
  return currentTime.toDate();
 }
}
A test z wykorzystaniem rula:
@Rule
public Clock clock = Clock.standard();

@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 // given
 Date date = new Date();
 clock.setFixedTime(date);
 Order order = new Order();

 // when
 Date createDate = order.getCreateDate();

 // then
 assertThat(createDate).isEqualTo(date);
}
Należy tylko pamiętać, że do tworzenia daty w zamówieniu używamy JodaTime. Implementacja Clocka nie wpływa na inne metody testowe (patrz. metoda after()), więc możemy w zależności od potrzeb korzystać z jego funkcjonalności lub nie.

Fluent builder do generowania fluent builderów cz.2

Tak jak wspominałem w części 1. postaram się przedstawić, jak można tworzyć buildery do klas, które nie korzystają z setterów i nie są w ciele klasy.

Może od razu na konkretnym przykładnie dla klasy:
public class Order {

 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;
 
 public void orderRealized(){
  //very complex implementation
  realized = true;
 }
}
Wykorzystując trochę przerobiony mechanizm generowanie builderów:
AbstractBuilderGenerator.forClass(Order.class)
    .withStaticCreate(true)
    .printBuilder();
Wynikiem jest interfejs dla naszego przyszłego buildera, w postaci abstrakcyjnej klasy i abstrakcyjnych metod:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder>{

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);
 public static OrderBuilder create(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Kod z sysout'a należy skopiować do pliku i mamy klasę buildera, którą możemy umieścić sobie w dowolnym miejscu, najlepiej gdzieś w folderze od testów.
OrderBuilder dodatkowo został wygenerowany z metodą create, która wywołuje fabrykę tworzącą instancję buildera na podstawie jego abstrakcyjnego interfejsu. To jest miejsce gdzie się dzieje trochę czarnej magii, tj. refleksje, dynamiczne proxy, generics. Z drugiej strony, który framework na tym w dzisiejszych czasach nie bazuje? Fabryka wykorzystuje domyślny konstruktor do utworzenia obiektu docelowego (może być prywatny), ale jeśli klasa nie ma takiego konstruktora, to można użyć przeładowanej metody createImplementation i przekazać obiekt docelowy utworzony przez jakikolwiek konstruktor.

Samo wykorzystanie:
Order realizedOrder = OrderBuilder.create().withRealized(true).build();
Dzięki temu, że interfejs buildera nie jest tak de facto nie interfejsem a klasa abstrakcyjną mamy pełną swobodę w tworzeniu dodatkowych metod buildera, które mogą korzystać z metod domenowych obiektu.

Zmodyfikowany interfejs:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder> {

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);

 public OrderBuilder realized() {
  Order order = targetObject();
  order.orderRealized();
  // other methods
  return builder();
 }

 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Korzystając z metod targetObject i builder (zwracające odpowiednio referencje na obiekt docelowy i samego buildera), możemy konstruować dodatkowe metody tak, aby nie przerywały mechanizmu fluent. Wykorzystanie:
Order realizedOrder = OrderBuilder.create()
    .realized()
    .withCreateDate(new Date())
    .build();

Zapraszam do forkowania i komentowania co jeszcze można by tu było usprawnić, biblioteczka jest właściwie w powijakach. Najwygodniej byłoby gdyby generowanie nie odbywało się do sysouta, tylko od razu do pliku. Niestety nie mam żadnego doświadczenia w tworzeniu pluginów eclipsowych...

piątek, 4 stycznia 2013

Fluent builder do generowania fluent builderów cz.1

Na samym początku dla wszystkich, którzy przykładają duża wagę do definicji, nie będzie tutaj mowa o wzorcu builder w pełnej swojej krasie, a raczej o jego uproszczonej wersji, która jest jednak częściej spotykana.

Stosując TDD, prędzej czy później dojdziemy do sytuacji, w której potrzebujemy spreparować sobie jakiś obiekt (niech to będzie np. encja domenowa) do testów. Jeśli obiekt ma dobrą enkapsulację, to nie mamy bezpośredniego dostępu do wszystkich pól, co za tym idzie niektóre z nich zostaną poprawnie zainicjowane dopiero po wywołaniu szeregu metod domenowych. Przykładowo jeśli potrzebujemy encje Order, ale jako zrealizowane zamówienie (pole realized = true), tylko i wyłączenie na potrzebuje testów, to dla mnie stratą czasu jest wywoływanie wielu metod (które nie rzadko są dość skomplikowane) tylko po to żeby ta flaga miała odpowiednią wartość.

Drogą na skróty jest tutaj użycie buildera, który znajduje się wewnątrz danej klasy, dzięki temu nie musimy sztucznie dodawać metod "set" do obiektu. Tworzenie takich builderów jest dość nudnym zajęciem dlatego proponuje generator builderów, który zrobi to za nas.

Dla klasy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
}
Wykorzystując generator, który domyślnie drukuje do konsoli:
SimpleBuilderGenerator.forClass(Sample.class)
    .withMethodPrefix("with")
    .printBuilder();
Po wklejeniu tego co zostało wygenrowane do głownej klasy otrzymujemy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
 
 /** 
  * Fluent builder for Sample
  * @formatter:off
  */
 public static Builder builder() {
  return new Builder();
 }

 public static class Builder {

  private final Sample sample = new Sample();

  public Builder withId(int id) { sample.id = id; return this; }
  public Builder withName(String name) { sample.name = name; return this; }
  public Builder withMap(Map<String, List<Integer>> map) { sample.map = map; return this; }

  public Sample build() { return sample;}
 }
 /** @formatter:on */
}
I samo wykorzystanie:
Sample sample = Sample.builder().withName("name").build();
Wszystko fajnie pięknie, ale taki builder ma jedną zasadniczą wadę - jego kod znajduje się w klasie produkcyjnej. Wymaga to od programisty przestrzegania zasady, żeby nie używać tego mechanizmu w kodzie produkcyjnym.

Właściwie cały ten post to preludium do właściwej treści, czyli tworzenie builderów, które są poza klasami produkcyjnymi i nie wymagają setterów, ale o tym w części 2.

Kod źródłowy generatora można znaleźć tutaj.