Montag, Juli 30, 2018

Links der Woche

  • Apache´s XMLBeans ist zurück. Das Projekt war bereits auf der Abschussliste und erlebt ein Comeback. Die Meldung und weitere Infos gibt es auf Heise.
  • Angular 6 ist (schon lange) da. Die neuen Features in der Zusammenfassung.
  • Für die Programmierer von morgen: Codecombat. Das könnte bei meinen Kindern funktionieren.
  • Spring Framework 5.0.8 ist erschienen.
  • Falls ihr eure Kommunikation verbessern möchtet, dann kann ich diesen Youtube Kanal empfehlen.

Montag, Juli 02, 2018

DTOs mit einem Interface

Frage: Lohnt es sich ein DTO (Data Transfer Object) mit einem Interface zu versehen? Aus dem Bauch heraus würde ich sagen nein. Eine Google-Suche nach 'Interface' und 'DTO' bzw. 'Value Object' scheint meine Annahme zu unterstützen. Siehe Stackexchange oder Stackoverflow. Letzter Beitrag erhielt von den Nutzern des Forums eine schlechte Bewertung. Die Begründung: closed as primarily opinion-based.

Und in dieser Bewertung steckt auch schon die Antwort. Die Antwort auf diese Frage ist stark abhängig von der persönlichen Meinung der beteiligten Person. Es gibt kein eindeutiges wahr oder falsch zu dieser Frage. Ich versuche einfach mal ein paar Argumente zu sammeln.

Wann lohnt es sich, ein Interface zu verwenden? Wenn ich mehr als eine Implementierung habe? Wenn mir nur eine sinnvolle Implementierung für ein Interface einfällt, ist es dann noch in Ordnung ein Interface für die Implementierung anzulegen? Bei einem DTO fällt es mir schwer, eine andere Implementierung als die Default-Implementierung vorzustellen. Was ist die Default-Implementierung für ein DTO? Ein Objekt mit ein oder mehreren Eigenschaften, versehen mit einer getter und/oder setter Methode. Erweitere ich das DTO um eine weitere Eigenschaft, muss ich das Interface ebenfalls erweitern, wenn ich möchte, dass die Eigenschaft nach außen sichtbar ist. Wenn ich das nicht möchte, benötigt das DTO nicht die neue Eigenschaft. Zwei DTOs, die die gleiche Schnittstelle implementieren, werden sich gleich Verhalten. Was für ein anderes Verhalten sollte das zweite DTO im Vergleich zu dem Ersten haben?

Wenn ich mich entscheide, dass ein Service ein DTO zurückliefert, treffe ich die architektonische Entscheidung, das ein Service nur banale DTOs zurückliefert. Ich würde vermutlich der allgemeinen Architekturentscheidung widersprechen, wenn ich was anderes als ein DTO zurückgebe. Insofern habe ich Zweifel, dass ein Interface für ein DTO einen Mehrwert bietet.

Allerdings gibt es keinen kategorischen Grund, Interfaces für DTOs nicht zu definieren. Es entsteht im Zweifel erst einmal nur mehr Code. Obwohl die Codevermeidung kein schlechtes Qualitätsziel ist.

Zum Abschluss habe ich ein etwas ungewöhnliches Beispiel für die Verwendung eines DTOs. Schaut euch mal dazu den folgenden Code an:

public interface DataTransferObject {
    Long getId();
    String getName();
    LocalDateTime getBirthday();
    
    default DefaultDataTransferObject newDTO(DataTransferObject dto) {
        DefaultDataTransferObject ddto = new DefaultDataTransferObject(dto.getId()); 
        ddto.setBirthday(dto.getBirthday());
        ddto.setName(dto.getName());
        return ddto;
    }

    public static class DefaultDataTransferObject implements DataTransferObject {
        private final Long id;
        private String name;
        private LocalDateTime birthday;

        public DefaultDataTransferObject(Long id) {
            this.id = id;
        }
        
        @Override
        public Long getId() {
            return id;
        }

        @Override
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }

        @Override
        public LocalDateTime getBirthday() {
            return birthday;
        }
        
        public void setBirthday(LocalDateTime birthday) {
            this.birthday = birthday;
        }        
    }
}

public class Service {
    public DataTransferObject read(Long id) {
        return DTOMapper.toDto(repository.find(id));
    }

    public DataTransferObject write(DataTransferObject dto) {
        Entity entity = repository.find(dto.getId());
        DTOMapper.from(dto).to(entity);
        return DTOMapper.to(entity);
    }
}

Die Idee ist, dass der Service, der das DTO returniert, in seiner Signatur als Return-Typ das Interface verwendet. Der Aufrufer des Service erhält erst einmal ein nicht veränderliches Objekt. Um Eigenschaften per Setter Methode zu ändern, muss der Aufrufer newDTO aufrufen und erhält eine Kopie des DTOs. An diesem kann er seine Änderungen vornehmen und ggf. das neue DTO einem Service übergeben.

Welchen Mehrwert hat diese Lösung? Nun zunächst steht dem Aufrufer das Original DTO zur Verfügung, welches nicht verändert werden kann. Das kann praktisch sein, wenn man Änderungen im Client mit dem Original vergleichen möchte. Ein Argument für dieses Konstrukt ist die explizite Kennzeichnung von Änderungen am DTO. Der Service-Benutzer muss sich aktiv entscheiden newDTO() auzurufen und erhält dann ein neues Objekt. Erst jetzt kann er die Setter-Methoden aufrufen. Ein unveränderliches DTO hat durchaus einen gewissen Mehrwert, insbesondere wenn man einen funktionalen Programmierstil pflegt.

AssertJ und java.util.List

AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...