pondělí 25. září 2017

Sémantický identifikátor entity

Na projektech korporátních i startapových jsem se setkal vždy s tím, že si servisní třídy mezi sebou vyměnují identifikátory entit.

Často je to např. Long, pokud je datovým podkladem SQL databáze ale setkávám se i se Stringem, když je entita ze vzdáleného systému volaného přes MQ, WebServices, Rest apod.

Problém pak nastává v tom, že se tyto identifikátory často pletou.

Například

public void updateTransaction(Long transactionId, 
                              Long userId, 
                              Long operatorId, 
                              Long clientId) {
 // ... do some nasty business logic
}

Není jasné, jestli userId je identifikátor entity User - nejspíš ano. A co operatorId? Je to taky z entity User? Obzvláště, když žádná entita Operator v doménovém modelu není. 


Co kdyby rozhraní vypadalo takto
public void updateTransaction(TransactionId transactionId, 
                              UserId userId, 
                              UserId operatorId, 
                              ClientId clientId) {
 // ... do some nasty business logic
}

Takto už je sématický význam parametrů jasnější. Co ale ve skutečnosti obsahuje třída UserId?
public final class UserId {
  private final Long value;
 
  private UserId(Long value) {
    this.value = value;
  }
 
  public Long getValue() {
    return value;
  }
 
  public static UserId of(Long value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(value); 
  }
 
  public static UserId of(String value) {
    if (value==null) throw new IllegalArgumentException("Parameter 'value' can't be null.");  
    return new UserId(Long.valueOf(value)); 
  }
}


Instance je immutable a je tedy thread-safe. Zároveň hlídá, že hodnota value je ne-null-ová. Může obsahovat tolik factory metod kolik je potřeba - v tomto případě umí instanciovat z Long a ze String. 

Co když používám Hibernate? Jak to mám namapovat? Můžeme nentitu nechat tak jak je - anotovanou nad attributy - pak Hibernate mapuje hodnoty přímo na atributy bez použití set a get metod.
  
@Entity
@Table(name = "COOL_USER")
public class User {
  @Id
  @GeneratedValue
  @Column(name = "USER_ID")
  private Long id;

  @Column(name = "FIRST_NAME")
  private String firstName;
   
  @Column(name = "LAST_NAME")
  private String lastName;

  public UserId getId() {
    return UserId.of(id);
  }

  public void setId(UserId id) {
    this.id = id.getId();
   }
    
    // other set get methods for firstName and lastName

}

Čitelnost servisního kódy je mnohem lepší a navíd kompilátor za vás dělá kontrolu, jestli jste parametry nepopletli. 

Myslím si, že podobný přístup by se dal použít i na jiné jednoduché datové typy - např. AccountNumber, BirthNumber, DateOfBirth místo String, String, Date. 

čtvrtek 20. dubna 2017

How to join strings with separator

import com.google.common.base.Joiner;
 
public static void main(final String[] args) {
  final String output = Joiner.on(", ").join("white", "blue", "red", "black");
  System.out.println("output = " + output);        
}
output = white, blue, red, black

pondělí 21. listopadu 2016

Peníze v Javě


Tentokrát bych chtěl rozebrat možnosti a moje zkušenosti, jak v kódu nakládat s penězi.

Datový typ - BigDecimal


Pro ukládání částky je rozhodně nutné používat datový typ BigDecimal  Jedině ten zaručuje, že nebude docházet k nečekanému zaokrouhlování za desetinou čárkou. BigDecimal snese v podstatě libovolnou přesnost a velikost čísla. Rozhodně nepoužívejte datové typy typu Float nebo Double.

Setkal jsem se i s aplikací, kde částky byly uchovávány jako celé číslo. Pro zobrazení se pak musela desetinná čárka uměle posouvat, tak aby dávala obsahově smysl. Fungovalo to dobře, ale myslím, že už je to přežité. Použil bych tento přístup jen v programovacím jazyku, který nemá BigDecimal alternativu.

Třída BigDecimal je immutable, takže se s instancemi ve vícevláknovém běhu pracuje bezpečně. Poskytuje všechny základní operace jako je sčítání, násobení a zaokrouhlování.

Identifikace měny


Peníze jsou ale ve skutečnosti dvojicí částky a měny.  Třídu, která by ale skládala dohromady částku a měnu přímo v JDK nemáme. Pokud máte částku a víte že jde o nějaké peníze, ale nevíte v jaké měně, tak je to docela problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price; // What currency is it ?

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }
    // geters …
}

Třídu pro měnu máme v Javě už od verze 1.4 -  Currency. Rozšířit entitu o další atribut by neměl být problém.

public class Product {
    private final Long productId;
    private final String name;
    private final BigDecimal price;
    private final Currency currency;

    public Product(final Long productId, 
                   final String name, 
                   final BigDecimal price, 
                   final Currency currency) {

        this.productId = productId;
        this.name = name;
        this.price = price;
        this.currency = currency;
    }

    // geters …
}

Pokud v jedné entitě máte více cen - např. prodejní, nákupní, apod. Duplikují se vám dvojice částka a měna a poměrně snadno při použití může dojít k tomu, že částky a měny poplete. Musíte také ohlídat, aby byla vždy zadaná celá dvojice. Pokud dovolíte zadat částku bez měny, opět je to problém.

Joda Money

Problém neexistence třídy pro peníze vyřešil Stephen Colebourne, mimochodem autor populární knihovny JodaTime. Naimplementoval knihovnou pod názvem Joda-Money. Ta obsahuje třídu jak pro měnu - CurrencyUnit tak pro magickou dvojici částka, měna - a to hned ve dvou různých třídách Money a BigMoney

Money je třída, která agreguje  dvojici částka (jako BigDecimal) a měna (jako CurrencyUnit). Navíc hlídá počet desetinných míst pro danou měnu. Třída BigMoney poskytuje vše co Money, ale nehlídá počet míst za desetinou čárkou pro danou měnu. To se hodí, pokud vám částky přicházejí ze zdroje, který nemáte úplně pod kontrolou. Konverze mezi oběma typy je samozřejmě přímočará.  Obě třídy jsou immutable, takže opět bezpečné pro práci ve vícevláknovém prostředí. 

Upravený příklad vypadá takto.

public class Product {
    private final Long productId;
    private final String name;
    private final Money price;
    
    public Product(final Long productId, 
                   final String name, 
                   final Money price) {

        this.productId = productId;
        this.name = name;
        this.price = price;
    }

    // geters ... 
}

Závěr

Pokud pracujete v rámci programu s penězi, doporučuji určitě zahrnout do projektu o jednu závislost navíc a důsledně používat datový typ Money resp. BigMoney, místo dvou nezávislých atributů pro částku a pro měnu. Předcházíte tak riziku, že atributy prohodíte. Navíc je kód mírně přehlednější. Líbí se mi i že místo obecného typu pro číslo použit naprosto konkrétní pro peníze.


čtvrtek 18. srpna 2016

Mockování emailové komunikace v integračních testech

Na projektu JdemeNaTo jsme narazili na problém, jak v selenium integračních testech pracovat s emaily.

Testovaná aplikace posílá emaily přes SMTP protokol. Test je pak musí přečíst a zpracovat. Samozřejmě, že email jako takový se nesmí dostat ven z integračního prostředí.

Poměrně dlouhou dobu jsme používali jednoduché řešení Dumbster. To spočívalo v tom, že si test vytvořil při svém spuštění mock SMTP server na dohodnutém portu, a test si pak přímo přečetl příchozí mail. Po skončení testu se port zase uzavřel.

Toto řešení jsme museli ale opustit v době, kdy máme celé integrační prostředí postavené na dockeru. Testy pouštíme stále pomocí mavenu na TeamCity proti testovanému serveru hostovaném v docker kontejneru. Každý docker kontejner má svoji ip adresu a namá ponětí o tom, kdo ho volá.

Hledali jsme vhodnou náhradu za Dumbster a našli jsme překvapivě robustní implementaci v javě GreenMail, který je určený právě pro testovací účely. Sympatická je i velká variabilita nasazení - poskytují docker image, tak i standalone aplikaci či jako war do Tomcatu. Na straně posílání mailů jsme nemuseli udělat žádnou úpravu. Na straně čtení jsme si napsali jednoduchou rutinu založenou na POP3 protokolu.

Pokud hledáte řešení, jak mockovat email server v interačních testech, mohu GreenMail určitě doporučit.