wtorek, 19 czerwca 2012

Value Object a prawidłowa implementacja


Z racji wszechobecnej mody na DDD, obiekty typu Value Object (VO) powstają jak grzyby po deszczu. Zaimplementowanie takiego obiektu wydaje się być trywialne. Jednak bazując na własnych doświadczeniach widziałem/zrobiłem wiele różnej jakości VO i dopiero teraz chyba mam pełny pogląd w tej sprawie. Przykładem niech będzie VO Email, służący do opakowania stringowej reprezentacji adresu email. Implementacja będzie bazowała na Hibernate, ale zaprezentowane rozwiazania są wspólne do większości frameworków od persystencji.
@ValueObject
@Access(AccessType.FIELD)
public class Email implements Serializable{
 private static final long serialVersionUID = -6632187560263057672L;
 @Basic
 private String value;


 /**
  * na potrzeby hibenrate prywatny konstruktor
  */
 @SuppressWarnings("unused")
 private Email() {}

 /**
  * @param email - poprawy adres email
  * @throws IllegalArgumentException jeśli adres email jest niepoprawny
  */
 public Email(String email) {
  if (!EmailValidator.getInstance().isValid(email)) {
   throw new IllegalArgumentException("podałeś nieprawidłowy format adresu email");
  }
  this.value = email;
 }

 @Override
 public boolean equals(Object obj) {
  if (obj == this)
   return true;
  if (!(obj instanceof Email))
   return true;
  Email email = (Email) obj;
  return new EqualsBuilder().append(value, email.value).isEquals();
 }

 @Override
 public int hashCode() {
  return new HashCodeBuilder().append(value).toHashCode();
 }

 @Override
 public String toString() {
  return value;
 }

 public String getValue() {
  return value;
 }
}
Na pierwszy rzut oka nic wielce okrywczego w tym kodzie nie ma. Mimo wszystko warto opisać kilka kluczowych aspektów. Po pierwsze warto zrobić sobie adnotację „marker” do oznaczania wszystkich VO. Łatwiej je wtedy chociażby wyszukiwać w projekcie.
/**
 * stereotyp do oznacznia klass o charakterze ValueObject, 
 *  - ich identyczność bazuje na nie na Id jak w przypadku encji, tylko na ich stanie
 *  - najczęśniej powinny być niemodyfikowalne
 *  - enkapsulują pola o podobnej semantyce,
 * @author aludwiko
 */
public @interface ValueObject {}
Kolejna adnotacja @Access służy do nadpisania po czym mapowane są pola. Niezależnie od tego czy klasa bazowa np. User, mapowana jest z wykorzystaniem getterów czy bezpośrednio pól, Email zawsze będzie mapowany bezpośrednio do pola. Dzięki temu nie musimy tworzyć settera i gettera, co poprawia nam enkapsulację. W moim przypadku getter został, ale jest on po prostu przydatny.

Z racji tego, że encje przeważnie implementują interfejs Serializable, od razu dodajemy go do VO, na pewno nic złego z tego nie wyniknie.

Konstruktor domyślny (wymagany przez Hibernata) został zaimplementowany jako prywatny, aby nikogo nie kusiło tworzenie „pustych” Emaili. Publiczny konstruktor gwarantuje nam, że nigdy utworzymy „błędnego” obiektu Email, co jest jedną z cech dobrego VO, tzn jego stan nie powinien być nieprawidłowy.

Taki konstruktor i wspomniany wcześniej brak settera powodują że Email spełnia kolejną ważna cechę VO, jest niemodyfikowalny. Jest to bardzo ważna cecha w kontekście VO. Equals i hashCode dopełniają minimalną implementację.

Ok, mamy VO, pytanie co dalej z nim zrobić. Jeśli chodzi o warstwę widoku, to należy stworzyć odpowiedni Converter w JSF, PropertyEditor w starym Springu, lub jakikolwiek inny mechanizm, który z wartości inputa utworzy nam obiekt Email. Z racji tego że nie mamy settera nie bindujemy na widoku wartości bezpośrednio do value
<h:inputtext id="email" value="#{user.email.value}">
tylko do samego email'a
<h:inputtext id="email" value="#{user.email}">
Przykładowa implementacji w springu:
public class EmailPropertyEditor extends PropertyEditorSupport {

 @Override
 public void setAsText(String text) throws IllegalArgumentException {
  if (StringUtils.isNotBlank(text)) {
   Email email = new Email(text);
   setValue(email);
  }
  else {
   setValue(null);
  }
 }

 @Override
 public String getAsText() {
  Email email = (Email) getValue();
  if (email != null) {
   return email.getValue();
  }
  else {
   return "";
  }
 }
}
W tym momencie mamy już obiekt z wypełnionym Email'em, teraz warto powiedzieć Hibernatowi jak ma zapisywać taki obiekt z Email'em do bazy. Są dwa główne podejścia, albo idziemy w kierunku @Embeddable i @Embedded albo Hibernate UserType.

Pierwsze podejście jest już proste i prawdopodobnie każdy miał z nim styczność. Chociaż np. dyskusja czy każdy obiekt @Embeddable jest automatycznie VO, nadal trwa i właściwie jestem po obu stronach w tym konflikcie.

Drugie podejście, choć trochę bardziej skomplikowane, daje ciekawy efekt w postaci możliwości tworzenia bardziej obiektowych zapytań, takich jak np:
Email email = new Email("aaaa@op.pl");
Criteria criteria = session.createCriteria(User.class).add(eq("email", email));
lub jako HQL
"from User WHERE email = :someEmail"
W tym przypadku musimy napisać własną implementację typu pola w klasie, tak aby hibernate wiedział jak zapisywać Emaila. W większości wypadków wystarczy zaimplementowanie interfejsu UserType. Jednak w tym przypadku chciałbym mieć możliwość tworzenia zapytań z wykorzystaniem LIKE:
Criteria criteria = session.createCriteria(User.class).add(ilike("email.value", "op.pl"));
Dostęp do pól VO w zapytaniach jest możliwy, ale musimy zaimplementować CompositeUserType, przykład implementacji:
/**
 * implementacja UserType'a dla VO {@link Email}
 * @author aludwiko
 */
public class EmailCompositeType implements CompositeUserType {

 /**
  * ORDER IS IMPORTANT! it must match the order the columns are defined in the property mapping
  */
 @Override
 public String[] getPropertyNames() {
  return new String[] { "value" };
 }
 @Override
 public Type[] getPropertyTypes() {
  return new Type[] { Hibernate.STRING };
 }
 @Override
 public Object getPropertyValue(Object component, int property) throws HibernateException {
  if (component == null) {
   return null;
  }
  Email email = (Email) component;
  switch (property) {
   case 0: {
    return email.getValue();
   }
   default: {
    throw new HibernateException("Invalid property index [" + property + "] for Email");
   }
  }
 }
 @Override
 public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
  throw new UnsupportedOperationException();
 }
 @Override
 public Class returnedClass() {
  return Email.class;
 }
 @Override
 public boolean equals(Object x, Object y) throws HibernateException {
  if (x == y) {
   return true;
  }
  if (x == null || y == null) {
   return false;
  }
  Email emailX = (Email) x;
  Email emailY = (Email) y;
  return emailX.equals(emailY);
 }
 @Override
 public int hashCode(Object x) throws HibernateException {
  return x.hashCode();
 }
 @Override
 public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
  String name = names[0];
  Object emailString = Hibernate.STRING.nullSafeGet(rs, name);
  if (emailString == null) {
   return null;
  }
  return new Email((String) emailString);
 }
 @Override
 public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
  if (value == null) {
   Hibernate.STRING.nullSafeSet(st, null, index);
  }
  else {
   Hibernate.STRING.nullSafeSet(st, ((Email) value).toString(), index);
  }
 }
 @Override
 public Object deepCopy(Object value) throws HibernateException {
  return value;
 }
 @Override
 public boolean isMutable() {
  return true;
 }
 @Override
 public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
  return (Serializable) value;
 }
 @Override
 public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
  return cached;
 }
 @Override
 public Object replace(Object original, Object target, SessionImplementor session, Object owner) throws HibernateException {
  return original;
 }
}
I samo wykorzystanie:
@Entity
public class User {
        //...
 @Column
 @Type(type = "package.EmailCompositeType")
 public Email getEmail() {
  return email;
 }
 public void setEmail(Email email) {
  this.email = email;
 }
        //...
}
Mam nadzieję, że temat został opisany kompleksowo i da się to przeczytać w skończonym czasie. Gdyby ktoś miał coś ciekawego do dodania, to zachęcam do komentowania.

piątek, 3 lutego 2012

Przekazywanie adnotacji do adnotacji.

Adnotacje mają pewne swoje ograniczenia. Pierwsze z nich to fakt, że danej adnotacji do danego targetu możemy użyć tylko raz.

Bazując na przykładzie z poprzedniego posta, jeśli w klasie Entity, mamy 2 pary pól do identycznej walidacji, np:
public class Entity {

    private Date from;
    private Date to;
   
    private Date activeFrom;
    private Date activeTo;
}

To użycie tej samej adnotacji walidującej dwa razy jest niedozwolone.
@CheckDates(dateFrom="from", dateTo="to")
@CheckDates(dateFrom="activeFrom", dateTo="activeTo")
public class Entity {

    private Date from;
    private Date to;
    
    private Date activeFrom;
    private Date activeTo;
}

Rozwiązaniem tego problemu jest wykorzystanie kolejnego ograniczenia adnotacji, mówięcego, że parametrem adnotacji może być tylko typ prymitywny, String, Class, enum, inna adnotacja, lub tablica 1-wymiarowa tablica wymienionych wcześniej klas.

Implementacja czegoś takiego mogłba by być następująca:
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = MultiCheckDatesValidator.class)
public @interface MultiCheckDates {

    String message() default "{pl.costam.MultiCheckDates}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    CheckDates[] value();
}

I sam walidator, który tak de facto w pętli wywołuje walidator z poprzedniego posta (musimy jedynie dopisać konstruktor):
public class MultiCheckDatesValidator implements ConstraintValidator<MultiCheckDates, Object> {

    private CheckDates[] checkDates;


    @Override
    public void initialize(MultiCheckDates constraintAnnotation) {

        checkDates = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext context) {

        boolean isValid = true;

        for (CheckDates checkDate : checkDates) {

            /**
             * tworzymy i wywołujemy walidator dla pojedyńczej pary from-to
             */
            if (!new CheckDatesValidator(checkDate).isValid(object, context)) {

                isValid = false;
            }
        }

        return isValid;
    }
}

I samo wywołanie:
@MultiCheckDates({
        @CheckDates(dateFrom = "from", dateTo = "to"),
        @CheckDates(dateFrom = "activeFrom", dateTo = "activeTo") })
public class Entity {

Za piękne może to i nie jest, ale warto mieć świadomość limitów wykorzystania adnotacji. Prawdopodobnie lepszym rozwiązaniem byłoby stworzenie osobnej klasy DateRange, jak zasugerował to Michał Gruca w swoim komentarzu.
Choć czasami zastajemy kod taki a nie inny i refaktor bywa bardzo bolesny.

Hibernate Validator (JSR 303) + mechanizm refleksji = walidacja absolutna

Przygarnięcie przez JavaEE projektu Hibernate Validator pod numerem JSR 303 było wg. mnie kolejnym dobrym krokiem w standaryzacji dobry rozwiązań z projektów opensourcowych.

Jednak po jakimś czasie używania (jakże przyjemnego) standardowych walidatorów, doszedłem do wniosku, że to za mało. Napisanie kilku własnych, które swoją drogą również tworzy się bardzo prosto, tylko na chwilę zaspokoiło moje potrzeby. Dopiero połączenie JSR 303 i mechanizmu refleksji w javie, daje maksymalne możliwości wykorzystania tego standardu.

Załóżmy, że mamy klasę która posiada dwie daty: od i do.
public class Entity {

    private Date from;
    private Date to;
}

Chcielibyśmy sprawdzić czy podane daty są po kolei, tj 'from' <= 'to'. Możemy napisać własny walidator dla klasy Entity, ale w projekcie takich klas z datami możemy mieć mnóstwo i dla każdej z nich należałoby stworzyć oddzielny walidator. Dzięki refleksją w javie możemy zrobić jeden uniwersalny, który jako parametry przyjmowałby 2 wartości, nazwa pola 'from', nazwa pola 'to'.
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckDatesValidator.class)
public @interface CheckDates {

    String message() default "{pl.costam.CheckDates}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String dateFrom();

    String dateTo();

}

I sam walidator:
public class CheckDatesValidator implements ConstraintValidator<CheckDates, Object> {

    private String dateFromFieldName;
    private String dateToFieldName;
    private String message;

    @Override
    public void initialize(CheckDates checkDates) {
        
        dateFromFieldName = checkDates.dateFrom();
        dateToFieldName = checkDates.dateTo();
        message = checkDates.message();
    }

    @Override
    public boolean isValid(Object object, ConstraintValidatorContext context) {

        boolean result = validateDates(object);

        if (!result) {
            
            //jeśli walidacja nie powiodła sie to tworzymy własny constraint - wskazujemy dla którego pola wystąpił błąd
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message)
                    .addNode(dateToFieldName)
                    .addConstraintViolation();
        }

        return result;

    }

    private boolean validateDates(Object object) {

        try {
            
            Date dateFrom = field(dateFromFieldName).ofType(Date.class).in(object).get();
            Date dateTo = field(dateToFieldName).ofType(Date.class).in(object).get();

            // sprawdzenie dat
            return checkDatesInterval(dateFrom, dateTo);
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

Poprzez refleksję można odwołać sie do pola używając właściwie "czystej" javy, ale wygląda to średnio w kodzie i lepiej użyć jakiegoś gotowego rozwiązania. Ja osobiście polecam biliotekę: FEST

Zastowanie adnotacji jest następujące:
@CheckDates(dateFrom="from", dateTo="to")
public class Entity {

    private Date from;
    private Date to;
}

środa, 9 listopada 2011

Hibernate L1 cache a cachowanie zapytań.

Niby wszyscy wiedzą jak działa działa L1 cache w Hibernate. W ramach jednej sesji hibernata, jeśli wczytamy sobie jakiś obiekt za pomocą np. funkcji .get(), to każde kolejne wywołanie tej funkcji nie odpytuje już bezpośrednio bazy danych, tylko zwraca ten obiekt właśnie z sesji. Pytanie, czy jesteśmy świadomi wszystkich konsekwencji takiego podejścia?

Proponuje zagadkę.
W bazie jest 2 użytkowników z tego samego miasta: Andrzej i Wojtek. Metoda getAllUsersFromCity() tworzy odpowiedni obiekt Criteria i zwraca listę użytkowników z danego miasta. Przyjmijmy, że kod funkcji testCache() wykonywany jest w ramach jednej sesji hibernata:

public void testCache(){
    printUsers();
    //w tym momecie ktoś inny edytuje jednego z użytkowników,
    //w zupełnie innej sesji lub bezpośrednio na bazie,
    //zmienia nazwe z Andrzej na Bartek
    printUsers();
}

public void printUsers(){
    List<User> users = dao.getAllUsersFromCity("city");
    foreach(User user : users){
        System.out.println(user.getName());
    }
}

Co zostanie wyświetlone na ekranie?
a)AndrzejWojtekAndrzejWojtek
b)AndrzejWojtekBartekWojtek

środa, 31 sierpnia 2011

Velocity - "is not a valid reference".

Jeśli masz problem z odwołaniem się w Velocity do metody jakiegoś obiektu, np.
$someObject.getSomeField()
i dostajesz komunikat:
... is not a valid reference
To warto sprawdzić czy klasa obiektu someObject jest publiczna, w przeciwnym razie otrzymasz wspomniany wyżej komunikat.

wtorek, 28 czerwca 2011

Logika + intuicja = inteligencja ?

Rzadko mi się to zdarza, ale czasem trzeba usiąść i przewartościować to i owo w swoim życiu. Bez obaw, nie będzie to tekst o uczuciach, związkach i innych takich:) Będzie o sposobie myślenia, przyswajania wiedzy i to niekoniecznie w stricte programistycznym kontekście.

Czy zdarzyło Ci się kiedyś, że poświeciłeś 2/3 dni na jakiś problem, zadanie - bez większych rezultatów, a nagle (czasami nawet kilka dni później), nie wiadomo skąd - oświecenie - Twój mózg dostarcza rozwiązanie, praktycznie na talerzu. Ja miałem tak przy całkach, czasami potrafiłem zmarnować cały dzień na 1 całkę, żeby rano obudzić się i ją po prostu ładnie rozpisać. Jeśli chcesz wiedzieć skąd się to bierze, to koniecznie musisz przeczytać:


Tutył tcorhę nie ceikawy, ale jśeli ztsawnaaisz się delazcgo potarsfiz pzreyczatć ten tkest bez wikęzsyh porblmóew, to opdoeiwdź na m.in. to pyatine zanjzdisez w tej kąsicże.

Co do przewartościowania, to musiałem zmienić swój pogląd dotyczący wszelkich aktywności o naturze bardziej "artystycznej", które uważałem, do tej pory, za nieprzydatne i zbędne (przynajmniej dla mnie). Myślałem, że logika jest głównym silnikiem mojego mózgu. Co nie mija się mocno z prawdą, aczkolwiek każdy z nas ma drugi silnik - zupełnie nielogiczny i bardzo abstrakcyjny. Intuicja, instynkt (tak nazywany jest ten silnik) wydają się nie do końca potrzebne w przypadku np. programowania, ale tak de facto dopiero one pozwalają w pełni rozwinąć skrzydła (właściwie czymkolwiek byśmy się nie zajmowali). To właśnie silnik nr 2 pozwala obudzić się z rozwiązaną całką przed oczyma. Jego możliwości są praktycznie nieskończone jak i bardzo nieprzewidywalne. Ważne jest aby dbać i rozwijać oba silniki. Jak sugeruje autor książki, właśnie aktywności wywodzące się z szeroko rozumianej sztuki, rozwijają ten drugi silnik. Wątpię żebym nagle zaczął pisać wiersze, ale dostrzegam sens np. plastyki (i innych podobnych przedmiotów) w szkole, które (pomijając to, że były moim zdaniem po prostu źle prowadzone) mogą przydać się w ogólnym rozwoju.

Tak samo krytycznie nastawiony byłem do różnego rodzaju "niestandardowych" sposobów nauczania. Nie twierdze, że nagle wszystkie nabierają dla mnie sensu, ale cześć technik, ma swoje naukowe uzasadnienie i muszę przyznać, że chciałbym kilka z nich wypróbować. Mam nadzieję, że wystarczająco zaciekawiłem, aczkolwiek ciężko streścić książkę, która sama w sobie jest streszczeniem wielu prac i badań naukowych.

Dla tych bardziej leniwych, polecam wykład, który w dużej mierze pokrywa się z tą książką:
http://www.infoq.com/presentations/Developing-Expertise-Dave-Thomas

Wyjściowym źródłem informacji dla mnie był m.in. blog:
http://art-of-software.blogspot.com/2011/06/trawienie-confitury.html
http://art-of-software.blogspot.com/2010/01/wspinaczka-do-profesjonalizmu.html

wtorek, 21 czerwca 2011

Partial mock - stosować, czy nie?

Na samym początku chcę zaznaczyć, że prawdopodobnie ten post nie będzie udzielał jednoznacznej odpowiedzi, jego celem (przynajmniej na chwilę obecną) jest zebranie wszystkich za i przeciw stosowaniu częściowego mockowania.

Zakładam, że ewentualnemu czytelnikowi sama idea mockowania jest znana. Partial mock danego obiektu to taka hybryda prawdziwego obiektu i mocka, innymi słowy część metod danego obiektu może być zmockowana, a cześć działać normalnie. Więcej o tym:
Co to daje? Przede wszystkim ułatwia testowanie, czy raczej samo napisanie testu. Wyobraźmy sobie sytuację, w której dana klasa ma jedną publiczną metodę, która korzysta z 20 innych prywatnych, pozagnieżdżanych między sobą. Załóżmy, że te 20 metod tworzy spójną całość i wydzielanie ich do osobnych klas byłoby bezsensowne. Napisanie testu dla takiej publicznej metody jest zajęciem dość karkołomnym. Taki test, który np. sprawdzałby czy metoda 17 robi coś tam poprawnie, zawiera w sobie całą masę mocków i innych obiektów, które umożliwiają taki przepływ w kodzie klasy, aby dojść do tej metody 17 i ją przetestować. Zrozumienie takiego testu dla człowieka, który go nie pisał jest dramatem.

Czy nie lepiej napisać oddzielny test dla metody 17? A potem odpowiednio sprawdzić, czy metoda ta jest wywoływana w innej metodzie, właśnie za pomocą partial mockingu? Jak dla mnie takie podejście jest o wiele przejrzystsze.

Konserwatywni w dziedzinie testowania, na pewno już się gotują, że piszę o testowaniu metod prywatnych, ale powiedzmy, że zmieniam im dostęp na pakietowy:). Tak, czy inaczej na pewno nie pochwalą partial mocków, bo to podejście śmierdzi, jest "niekoszerne".

Ogólnie zgadzam się z nimi w jednej kwestii, na pewno konieczność zastosowanie partial mockingu do testów, jest bardzo wyraźnym sygnałem, że z naszym kodem jest coś nie tak i prawdopodobnie lepszym wyjściem będzie jego refaktoring.

Jednakże istnieją sytuacje (jak chociażby legacy code), gdzie partial mock jest nie dość, że wygodnym, to czytelnym rozwiązaniem.

Oczywiście nie jestem w dziedzinie testów aż takim ekspertem jakbym chciał, dlatego jeśli istnieją inne przesłanki za lub przeciw partial mockom, to proszę o komentarze.

Artykuły o partial mocking:
http://nathan.crause.name/entries/programming/partial-mocking-stubbing-and-why-it-s-not-evil
http://monkeyisland.pl/2009/01/13/subclass-and-override-vs-partial-mocking-vs-refactoring/
http://stackoverflow.com/questions/3806977/partial-mocking-as-code-smell