piątek, 16 maja 2014

Spring MVC i komunikaty walidacji JSR-303

Człowiek potrzebował zrobić trywialną rzecz, komunikaty o błędach walidacji JSR 303 trzymać w tym samym MessageSource, co inne propertiesy aplikacji, oraz korzystać ze springowego DefaultMessageCodesResolver. A skończyło się jak zwykle na deep-debugging...

Może od razu przykład. Dla klasy:
public class Dorosly {
 @Range(min = 18, max = 100)
 private Integer wiek;
}
Definicja komunikatu może wyglądać tak:
org.hibernate.validator.constraints.Range.message = pole musi byc z przedzialu {min} - {max}

wtorek, 28 stycznia 2014

Git - zarządzanie aliasami

Poznając Gita prędzej, czy później zaczynamy tworzyć własne aliasy. Z racji tego, że Git jest rozproszonym systemem kontroli wersji, to każdy może mieć lokalnie dowolny zestaw aliasów. W momencie kiedy spójność aliasów zaczyna mieć krytyczne znaczenie dla procesu continuous delivery w danej firmie, pojawia się problem jak tymi aliasami zarządzać, tak aby „zmusić” programistów do używania tych najbardziej aktualnych. Jednak zanim o tym, to może przykład jakiś aliasów, które muszą być takie same u każdego z programistów.
Używając Gita, bardzo często korzystamy (a jeśli nie, to nie wykorzystujemy w pełni jego potencjału) z modelu branchowania podobnego do tego. W modelu tym zależy nam, żeby jakoś wyróżnić gałęzie releasowe od rozwojowych. Ustalamy, że gałąź releasowa jest odbijana zawsze od mastera i jej nazwa ma następujący format:

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.