Software Architecture & Design - JAX https://jax.de/blog/software-architecture-design/ Java, Architecture & Software Innovation Thu, 16 Jun 2022 13:49:37 +0000 de-DE hourly 1 https://wordpress.org/?v=6.4.2 Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign https://jax.de/blog/software-architecture-design/services-im-zwiegespraech-synchrone-kommunikation-zwischen-rest-services-mithilfe-von-openfeign/ Fri, 18 Jan 2019 17:03:00 +0000 https://jax.de/?p=66668 Wer sich heutige Softwareprojekte oder -architekturen anschaut, steht immer wieder vor ähnlichen Herausforderungen. Eine davon ist die Kommunikation zwischen Services. Asynchron oder synchron, das ist hier die Frage.

The post Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign appeared first on JAX.

]]>

von Jörn Hameister
Ob es Microservices sein müssen oder ob es sich um Anwendungen in einer Systemlandschaft handelt, spielt keine Rolle. Wenn zwei Services miteinander kommunizieren sollen, besteht die Möglichkeit, dass die Kommunikation asynchron (etwa über Messages mit Kafka oder JMS) oder synchron (beispielsweise über REST) abläuft. Auf lange Sicht bietet die asychrone Kommunikation eine Reihe von Vorteilen, wie lose Kopplung von Services und Resilience. Allerdings wird trotzdem häufig die synchrone Kommunikation bevorzugt, weil sie leichter zu verstehen und zu implementieren ist. Zusätzlich fallen auch die Fehlersuche und das Debugging leichter. Deshalb stelle ich hier ein Framework vor, mit dem sich relativ einfach, elegant und übersichtlich die synchrone Kommunikation zwischen REST Services realisieren lässt: OpenFeign [1].

 

Erst nach dem Problem fragen

Wer kennt es nicht: In einer Systemlandschaft existiert ein Service mit einer REST-Schnittstelle, der Daten bereitstellt, die in einem anderen Service benötigt werden. Dazu gehören zum Beispiel Rechnungen, Kundendaten oder Wetterinformationen. Als Erstes stellt sich dann die Frage, mit welcher Technologie und welchem Framework der Service angebunden werden kann und welches Datenformat benutzt werden soll oder muss. JSON, XML, binär oder ein proprietäres Format?

In unserem Artikel gehen wir davon aus, dass JSON als Format zum Einsatz kommt. OpenFeign unterstützt auch alle anderen Formate und bietet die Möglichkeit, eigene proprietäre Formate zu ergänzen, sodass sie verarbeitet werden können. Wenn ein Service mit REST-Schnittstelle angesprochen werden soll, versucht man zuerst oft, den Service mit einem einfachen HTTP Call anzusprechen und die benötigten Daten abzufragen. Wenn von der Schnittstelle nur ein Integer oder String als Wert zurückkommt, ist das eventuell sogar ausreichend. Allerdings ist es oft so, dass komplexe Objekte (Entities, DTOs, …) an der Schnittstelle als JSON zurückgeliefert werden.

Für diesen Fall können wir beispielsweise den Jackson Mapper [2] ergänzen, um die Serialisierung und Deserialisierung der Objekte zu realisieren. Die Alternative ist das REST-Template von Spring. Es bietet sich an, wenn man sowieso im Spring-Boot-Umfeld unterwegs ist. Wer das REST-Template schon einmal eingesetzt hat, weiß, dass man jedes Mal überlegt, welche API-Methode (exchange, getForEntity usw.) man verwenden soll und wie die Parameter gesetzt werden müssen, um den gewünschten Wert abzufragen. Am Ende landet man meistens bei exchange, schaut wieder in die Dokumentation und sucht Codebeispiele, wie die Syntax genau aussieht.

Aus meiner Sicht ist der Java-Code mit seiner Fehlerbehandlung und dem Exception Handling immer wieder recht aufgebläht. Viel praktischer wäre es doch, wenn man einfach nur eine Clientschnittstelle beschreiben würde. Sie gibt an, wie die Service-Schnittstelle angesprochen werden soll. Das bedeutet, wir müssen uns nicht um die Fehlerbehandlung und die technischen Details kümmern. OpenFeign, ehemals Netflix Feign, ermöglicht beides. Schauen wir uns im ersten Schritt an einem kleinen Beispiel an, wie das funktioniert. Später wird an einem komplexeren API demonstriert, welche Möglichkeiten es gibt, um OpenFeign so zu erweitern, dass auch SPDY [3] verarbeitet werden kann.

ItemService

Anhand eines ItemStores, der Items (Dinge) verwaltet und über eine einfache REST-Schnittstelle angesprochen werden kann, erkennen wir, wie OpenFeign generell benutzt wird und funktioniert. Anfangs werfen wir einen kurzen Blick darauf, wie der Zugriff auf die Schnittstelle mit dem REST-Template oder einer http-Verbindung aussehen kann, um klar zu machen, welche Vorteile OpenFeign bietet. Die REST-Schnittstelle des ItemStore findet sich in Listing 1.

Listing 1: „ItemStore“ REST-Interface

@GetMapping(value = "/item")
ResponseEntity<List<Item>> getAllItems()

@PostMapping(value = "/item")
ResponseEntity<Item> createItem(@RequestBody Item item)

@PutMapping(value = "/item")
ResponseEntity<Item> updateItem(@RequestBody Item item)

@DeleteMapping(value = "/item/{id}")
ResponseEntity<Item> deleteItem(@PathVariable("id") long id)

@GetMapping(value = "/item/{location}")
ResponseEntity<List<Item>> getItemAtLocation(@PathVariable("location") String
location)

 

Es ist eine recht überschaubare Schnittstelle mit einer Methode zum Anlegen (createItem), Ändern (updateItem), Löschen (deleteItem) und Suchen (getItemAtLocation) von Items.

 

REST-Template

Wenn man mit der Klasse RestTemplate auf den Service zugreifen möchte, gestaltet sich das so:

RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Item[]> responseEntity =
restTemplate.getForEntity("http://localhost:8080/item", Item[].class);
List<Item> listWithItems = Arrays.asList(responseEntity.getBody());

Hier verwenden wir die Methode getForEntity, um alle Items abzufragen. Eine weitere Variante, um lesend mit GET auf den Service zuzugreifen, kann so aussehen:


ResponseEntity<List<Item>> rateResponse =
  restTemplate.exchange("http://localhost:8080/item",
  HttpMethod.GET, null, new ParameterizedTypeReference<List<Item>>() {
  });
  List<Item> itemList = rateResponse.getBody();

Hier wird die exchange-Methode benutzt, um alle Items abzufragen und das Ergebnis in einer Liste zu erhalten. Sobald wir allerdings nicht nur lesend auf die Schnittstelle zugreifen möchten, sondern auch PUT und POST benutzen, müssen wir die Funktion exchange verwenden.

RestTemplate restTemplate = new RestTemplate();
HttpEntity<Item> request = new HttpEntity<>(item);
ResponseEntity<Item> response = restTemplate.exchange("http://localhost:8080/item",
HttpMethod.POST, request, Item.class);

In diesem Beispiel legen wir ein neues Item über das REST API an. Das bedeutet, die Methode createItem wird aus dem Interface in Listing 1 aufgerufen und die Response enthält das neu angelegte Item.

HttpConnection

Natürlich kann man den lesenden Zugriff auch mit einer einfachen HttpConnection und mit dem Jackson ObjectMapper lösen. Allerdings wird schon bei GET deutlich, dass extrem viel Boilerplate-Code entsteht und eine aufwendige Fehlerbehandlung dazukommt (Listing 2).

Listing 2: „HttpConnection“ für GET

private static List<Item> httpClientGet() throws IOException {
  URL url = new URL("http://localhost:8080/item");
  HttpURLConnection con = (HttpURLConnection) url.openConnection();
  con.setRequestMethod("GET");

  BufferedReader in = new BufferedReader(
  new InputStreamReader(con.getInputStream()));
  String inputLine;
  StringBuffer content = new StringBuffer();
  while ((inputLine = in.readLine()) != null) {
  content.append(inputLine);
  }
  in.close();

    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.readValue(content.toString(), new
TypeReference<List<Item>>() { });
}

Außerdem ist bei diesem Ansatz auch die Gefahr größer, dass Fehler passieren. Beispielsweise weil vergessen wird, die Streams und Connections zu schließen. Eine andere Fehlerquelle liegt darin, dass wie im Beispiel kein Timeout von etwa fünf Sekunden mit con.setReadTimeout(5000); gesetzt wurde. Es ist eindeutig, dass das keine gute Lösung ist, die man für ein umfangreiches API implementieren und testen möchte.

Mit OpenFeign

Nachdem wir uns angeschaut haben, wie die REST-Schnittstelle mit dem REST-Template und mit HttpConnection angesprochen werden kann, kommen wir dazu, wie sich das mit OpenFeign lösen lässt. Um die Service-Schnittstelle aus Listing 1 mit OpenFeign anzusprechen, legen wir schlicht ein Interface an (Listing 3).

Listing 3: OpenFeign-Interface


package org.hameister.itemmanager;

import feign.Headers;
import feign.Param;
import feign.RequestLine;

import java.util.List;

public interface ItemStoreClient {

  @RequestLine("GET /item/")
  List<Item> getItems();

  @RequestLine("POST /item/")
  @Headers("Content-Type: application/json")
  Item createItem(Item item);

  @RequestLine("PUT /item/")
  @Headers("Content-Type: application/json")
  Item updateItem(Item item);

  @RequestLine("DELETE /item/{id}")
  void deleteItem(@Param("id") String id);

  @RequestLine("GET /item/{location}")
  List<Item> getItemAtLocation(@Param("location") String location);
}

Auf den ersten Blick ist deutlich, dass es nahezu identisch zum Service-Interface ist und keinerlei Boilerplate-Code enthält. Man beschreibt nur die Schnittstelle des Service mit dem Pfad der Operation und den Parametern und legt den Content-Type fest.

Mit der Annotation @RequestLine(“GET /item/”) geben wir die Operation und den Pfad an, der beschreibt, wo die Methode zu finden ist.

Um dieses Interface zu benutzen, lässt sich mit dem Feign.Builder einfach ein Client erzeugen und anschließend übers Interface auf die REST-Schnittstelle des ItemStore zugreifen (Listing 4).

Listing 4: OpenFeign-Client

package org.hameister.itemmanager;

import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;

import java.time.LocalDate;
import java.util.List;

public class ItemManager {

  public static void main(String[] args) {

    ItemStoreClient api = Feign.builder()
      .encoder(new JacksonEncoder())
      .decoder(new JacksonDecoder())
      .target(ItemStoreClient.class, "http://localhost:8080");

    }
}

Dem Builder kommunizieren wir, welche Decoder und Encoder er verwenden soll, wo der Server mit der REST-Schnittstelle läuft und welches OpenFeign-Interface er verwenden soll. In unserem Beispiel nutzen wir den JacksonDecoder und JacksonEncoder. Diverse andere Standardencoder und -decoder zu verwenden, wäre ebenfalls möglich. Beispielsweise für Gson zum Serialisieren und Deserialisieren von Java-Objekten, JAXB zum Serialisieren und Deserialisieren von XML und SAX zum Serialisieren von XML.

Außerdem lässt sich im Builder das Logging konfigurieren, indem wir einen Logger ergänzen:


Feign.builder().logger(new Slf4jLogger())

Zusätzlich kann man einen Client definieren, der Dinge wie SPDY erledigt.


SwapiFeign api = Feign.builder()
 .client(new OkHttpClient())

Es ist auch möglich, Ribbon für das clientseitige Loadbalancing hinzuzufügen:


SwapiFeign api = Feign.builder().client(new RibbonClient())

Außerdem können wir eigene Encoder und Decoder implementieren und registrieren, sodass proprietäre Formate unterstützt werden können. Dazu muss nur das jeweilige Interface implementiert werden. Für den Decoder:


public class MyCustomDecoder implements Decoder {
  @Override
  public Object decode(Response response, Type type) throws IOException,
DecodeException, FeignException {
    return ...;
  }
}


Für den Encoder:


public class MyCustomEncoder implements Encoder {
  @Override
  public void encode(Object o, Type type, RequestTemplate requestTemplate) throws
EncodeException {
  ...
    }
}


Diese Encoder und Decoder müssen anschließend wie die Standardencoder und -decoder

registriert werden, wenn der Client mit dem Builder erstellt wird. Nicht zu vergessen, dass man bei der Definition des Clientinterface auch direkt Hystrix integrieren kann. Dafür gibt es einen HystrixFeign Builder, der genauso benutzt wird wie der Standard-Builder.


ItemStoreClient api = HystrixFeign.builder()
  .target(ItemStoreClient.class, "http://localhost:8080");


Er ermöglicht uns, die Schnittstelle um ein fehlertolerantes Verhalten zu erweitern. Beispielsweise, wenn der Service nicht oder nur langsam antwortet. Gerade in einem Umfeld, in dem mehrere Services miteinander kommunizieren, lässt sich dadurch verhindern, dass der Ausfall eines Service das Gesamtsystem zum Stehen bringt. Das war es auch schon. Anschließend dient der erstellte ItemStoreClient dazu, über die REST-Schnittstelle auf den ItemStore zuzugreifen.

In Listing 4 zeigt sich, wie der ItemStoreClient erstellt wird. Anschließend können die Methoden übers Interface direkt aufgerufen werden: List<Item> items = api.getItems();

Das Anlegen von Items funktioniert so:


Item item = new Item();
item.setDescription("New Item");
item.setLocation("Schrank 5A");
item.setItemdate(LocalDate.now());
Item newItem = api.createItem(item);


Ein Item zu ändern lässt sich analog durchführen:


newItem.setLocation("Schrank 5B");
Item updateItem = api.updateItem(newItem);


Um ein Item zu löschen, muss die jeweilige ID übergeben werden: api.deleteItem(“1”);.Das ist im Vergleich zum REST-Template oder dem HttpClient erheblich eleganter und verständlicher. Anzumerken ist, dass bei allen Ansätzen auf Clientseite ein DTO für das Item vorhanden sein muss. Entweder wir kopieren die Klasse aus dem Item-Service oder legen eine neue Klasse an (Listing 5).

Listing 5:  Item-DTO

@Data
public class Item {

  Long id;

  private String description;
  private String location;
  private LocalDate itemdate;

  public Item() {
  }
}

 

In dem DTO-Item haben wir Lombok [4] verwendet, um die Getter und Setter automatisch generieren zu lassen.

SWAPI

Wir haben uns angeschaut, wie OpenFeign generell bei einer einfachen Schnittstelle verwendet werden kann und welche Vorteile es gegenüber anderen Ansätzen mitbringt. Jetzt wenden wir uns dem zu, was OpenFeign noch bietet. Das soll am Beispiel von SWAPI (The Star Wars API) gezeigt werden. Dabei handelt es sich um eine öffentliche REST-Schnittstelle, über die man Personen, Filme, Raumschiffe und Planeten aus dem Star-Wars-Universum abfragen kann. Die Schnittstelle ist unter dem URL https://swapi.co zu erreichen. Wie auch schon bei dem Beispiel oben legen wir als Erstes ein Interface für die Schnittstelle an (Listing 6).

Listing 6:  SWAPI-Interface

public interface SwapiFeign {
  @RequestLine("GET /planets/{id}")
  Planet getPlanet(@Param("id") String id);

  @RequestLine("GET /planets/")
  GenericList<Planet> getPlanets();

  @RequestLine("GET /films/")
  GenericList<Film> getFilms();

  @RequestLine("GET /people/")
  GenericList<People> getPeople();

  @RequestLine("GET /starships/")
  GenericList<Starship> getStarships();

  @RequestLine("GET /vehicles/")
  GenericList<Vehicle> getVehicles();

}

Wir müssen für die Rückgabewerte noch DTOs anlegen. Im Vergleich zum anderen Beispiel benötigt man außerdem noch eine Generic List, weil das API die Rückgabewerte untereinander verlinkt. Das heißt, die Rückgabewerte enthalten immer einen Link auf das vorhergehende und nächste Element (Listing 7).

Listing 7:  „GenericList“

public class GenericList<T> {
  public int count;
  public String next;
  public String previous;

  public List<T> results;
}

Anschließend lässt sich wieder ein Client erstellen. Er ermöglicht, die Daten über die REST-Schnittstelle abzufragen. Auch im folgenden Beispiel werden ein JacksonEncoder und ein JacksonDecoder verwendet, um die JSON-Daten von der REST-Schnittstelle zu serialisieren und zu deserialisieren.

SwapiFeign api = Feign.builder()
  .encoder(new JacksonEncoder())
  .decoder(new JacksonDecoder())
  .client(new OkHttpClient())
  .target(SwapiFeign.class, "http://swapi.co/api");

Beim Erstellen des Clients fällt auf, dass der OkHttpClient() gesetzt wird. Das ist notwendig, damit SPDY, HTTP/2 und TLS des REST-Interface bedient werden können. Der komplette Quellcode zum OkHttpClient steht im GitHub Repository zu dem Artikel zur Verfügung [5].

Mit dem api-Objekt lässt sich nun die Schnittstelle ansprechen:

GenericList<Starship> starships = api.getStarships();

Listing 8 zeigt exemplarisch das DTO für das Starship.

Listing 8:  „Starship“

@Data
public class Starship {

  private String name;
  private String model;
  private String manufacturer;
  private String costs_in_credits;
  private String length;
  private String max_atmosphering_speed;
  private String crew;

  private String cargo_capacity;
  private String consumables;
  private String hyperdrive_rating;
  private String MGLT;
  private String starship_class;

  private List<People> pilots;
  private List<Film> films;
  private String created;
  private String edited;
  private String url;

  public Starship() {

  }
}

Um das Schema, also die Felder eines Starships herauszufinden, kann man einfach das API befragen, das unter [6] zu erreichen ist. Auch dies ist eine REST-Schnittstelle, die sich abfragen lässt:

Schema schema = getSchema("https://swapi.co/api/starships/schema");

Wobei das Schema-DTO aussieht wie in Listing 9

Listing 9:  Schema-DTO

@JsonIgnoreProperties(ignoreUnknown = true)
public class Schema {
  public List<String>required;
  public Map<String, Properties> properties;
  public String type;
  public String title;
  public String description;

  public Schema() {
  }
}

Und das verwendete Properties DTO so:

public class Properties {
  public String type;
  public String format;
  public String description;
}

Die getSchema()-Methode mit dem OkHttpClient zum Abfragen des Schemas findet sich in Listing 10.

Listing 10:  „getSchema“-Methode

private static Schema getSchema(String url) throws IOException {
  okhttp3.OkHttpClient okHttpClient = new okhttp3.OkHttpClient();

  Request request = new Request.Builder()
  .url(url)
  .get()
  .build();
  Response response = okHttpClient.newCall(request).execute();
  ObjectMapper objectMapper = new ObjectMapper();
  Schema schema =
objectMapper.readerFor(Schema.class).readValue(response.body().string());
  return schema;
}

 

Hier haben wir bewusst darauf verzichtet, OpenFeign einzusetzen, um zum Abschluss noch einmal zu verdeutlichen, dass der Quellcode ohne OpenFeign länger ist als mit OpenFeign. Zu beachten ist, dass das Exception Handling hier weitgehend ignoriert wurde, indem die IOExceptions einfach an den Aufrufenden zurückgeworfen und nicht behandelt werden.

Um noch einmal zu unterstreichen, wie einfach die Abfrage mit OpenFeign funktioniert, definieren wir zuerst ein Interface:

public interface SwapiSchemaClient {

  @RequestLine("GET ")
  Schema getSchema();
}

Anschließend kann ein Feign-Client mit dem Builder erstellt und daraufhin das Schema

abgefragt werden (Listing 11).

Listing 11:  „SchemaClient“

SwapiSchemaClient api = Feign.builder()
  .encoder(new JacksonEncoder())
  .decoder(new JacksonDecoder())
  .client(new OkHttpClient())
  .target(SwapiSchemaClient.class, "https://swapi.co/api/starships/schema");

Schema schema = api.getSchema();

 

Weniger selbst implementieren

OpenFeign ist eine elegante Möglichkeit, REST-Schnittstellen anzusprechen. Der Anwender bekommt eine Menge Features quasi geschenkt, die er normalerweise selbst implementieren müsste. Allerdings ist es nur eine von vielen Möglichkeiten. Wie so oft bei der Softwareentwicklung muss man immer genau schauen, in welchem Kontext man sich bewegt, welche Rahmenbedingungen es gibt und was dann die beste Lösung in dem Projekt ist. Einen kurzen Einführungsvortrag zu OpenFeign hat Igor Laborie bei der Devoxx 2016 in Belgien gehalten [7].

Anmerken sollte man vielleicht noch, dass ab Java 11 ein HttpClient fester Bestandteil von Java (JEP 321) ist, der sowohl synchrone, als auch asynchrone Requests absetzen kann [8].

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

Links & Literatur
[1] OpenFeign: https://github.com/OpenFeign/feign
[2] Jackson Mapper: https://github.com/FasterXML/jackson
[3] SPDY: https://de.wikipedia.org/wiki/SPDY
[4] Project Lombok: https://projectlombok.org
[5] https://github.com/hameister/ItemStoreFeignClient
[6] SWAPI: https://swapi.co/api/starships/schema
[7] OpenFeign in Action: https://youtu.be/kO3Zqk_6HV4
[8] Java 11 HttpClient, Gson, Gradle, and Modularization: https://kousenit.org/2018/09/22/java-11-httpclient-gson-gradle-and-modularization/

The post Services im Zwiegespräch: Synchrone Kommunikation zwischen REST Services mithilfe von OpenFeign appeared first on JAX.

]]>
Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ https://jax.de/blog/software-architecture-design/software-architektur-heute-es-geht-um-menschen-technologien-sind-sekundaer/ Thu, 25 Oct 2018 11:26:39 +0000 https://jax.de/?p=65554 Software-Architektur galt lange als die Disziplin, um in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Stabilität, Sicherheit, Planbarkeit stand im Vordergrund. Wir haben uns mit Henning Schwentner darüber unterhalten, wie sich dieses Bild verändert hat und welche Rolle Trends wie DevOps und DDD dabei spielen.

The post Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

]]>
W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

Henning Schwentner: Beide Rollen sind untrennbar. Stabilität im Großen bekommen wir über Flexibilität im Kleinen. Das Interessante ist ja, dass insbesondere die großen Systeme, weil sie am längsten leben, gerade deshalb am flexibelsten sein müssen. Deswegen finde ich es gut, dass Trends wie Microservices und Self-Contained Systems die (im Prinzip alten) Ideen, wie man ein System vernünftig modularisiert, im Mainstream ankommen lassen. Der wichtigste Punkt ist: Wir wollen nicht ein großes Domänenmodell haben, sondern mehrere kleine. Die kleinen Domänenmodelle können dann verständlich, beherrschbar und flexibel sein.

W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden?

Henning Schwentner: Erstens: sich immer und immer wieder klar machen, dass Software kein Selbstzweck ist. Software (und auch Softwarearchitektur) bauen wir nicht für uns selbst, sondern für den Fachexperten. Zweitens: nicht nach dem einen Domänenmodell streben, das alle Probleme auf einmal löst. Das wird nämlich viel zu groß, fehleranfällig und schwer verständlich. Interessanterweise also gleichzeig instabil und unflexibel. Stattdessen wollen wir mehrere kleine Modelle.

W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

Henning Schwentner: Mir geht es da wie vielen: Ich freue mich, dass die unnatürliche Trennung von Entwicklung und Betrieb aufgehoben wird. Als Entwickler und Architekt wird man jetzt von Anfang an darauf fokussiert, nicht eine ausführbare Datei, sonderen laufende Software auszuliefern. Und nur die hat Wert für den eigentlich wichtigen Menschen – unseren Anwender.

W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

Henning Schwentner: Von Jerry Weinberg wissen wir: »No matter how it looks at first, it’s always a people problem.« Die erste Aufgabe jedes Menschen, der mit Softwareentwicklung beschäftigt ist, ob er nun Programmierer, Architekt, Projektmanager oder wie auch immer heißt, ist, mit anderen Menschen zu kommunizieren. Mit Computern zu kommunizieren kommt frühestens auf Platz zwei.

Technologien sind nicht unwichtig, aber sekundär. Frag dich mal selbst: Von wievielen Programmen und Apps, die du einsetzt, weißt du, welche Programmiersprache, Frameworks usw. darin verwendet wird? Ist dir das wichtig? Oder willst du lieber, dass die ihren Job machen?

Die Gemeinheit ist: In der Software-Entwicklung gilt das Anna-Karenina-Prinzip, d.h. ein Projekt kann nur dann erfolgreich sein, wenn alle Faktoren stimmen. Technologien kennen und beherrschen wird deshalb immer wichtig und nötig sein.

W-JAX: Auf der W-JAX wird es ein Lab zum Thema „Event Storming“ geben. Wie funktioniert Event Storming – und weshalb sollte man es machen?

Henning Schwentner: Die Grundaufgabe von Software ist, unseren Anwender bei seiner Arbeit zu unterstützen. Damit wir das tun können, müssen wir seine Arbeit (d.h. die Domäne) verstehen. Event Storming ist ein Werkzeug, das uns dabei hilft. Bewaffnet mit haufenweise Klebezetteln wird Wissen aufgebaut, ausgetauscht und vertieft. Es hilft uns dabei, eine gemeinsame Sprache, Kontextgrenzen und Domänenmodelle zu finden.

W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?
Henning Schwentner: Mir ist das Thema „Domäne verstehen, um die richtige Software zu bauen“ sehr wichtig. Neben Event Storming ist Domain Storytelling ein tolles Werkzeug dafür. Wir lassen die Anwender ihre Geschichte erzählen. Dabei zeichnen wir sie mit einer einfach zu verstehenden Bildsprache auf. Die entstehenden Bilder (die sogenannten Domain Stories) verwendet man, um direkt rückzukoppeln, ob wir die Anwender richtig verstanden haben. Mehr Infos gibt’s auf domainstorytelling.org und hoffentlich bald in dem Buch, das Stefan Hofer und ich gerade darüber schreiben.

W-JAX: Vielen Dank für dieses Interview!

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

 

 

The post Software-Architektur heute: „Es geht um Menschen, Technologien sind sekundär“ appeared first on JAX.

]]>
Was Sie bei der Einführung von APIs wissen sollten https://jax.de/blog/software-architecture-design/was-sie-bei-der-einfuehrung-von-apis-wissen-sollten/ Thu, 04 Oct 2018 13:02:31 +0000 https://jax.de/?p=65319 Jede noch so gut definierte Schnittstelle kann an einen Punkt kommen, an dem sie weiterentwickelt werden muss. Welche Herausforderungen bei der Einführung von APIs zu bewältigen sind, verrät uns Arne Limburg im Interview.

The post Was Sie bei der Einführung von APIs wissen sollten appeared first on JAX.

]]>
W-JAX: Viele Unternehmen führen derzeit APIs ein, die ältere technische Lösungen wie SOA, ESBs und/oder monolithische Systeme ersetzen. Weshalb eigentlich? Warum hat die sogenannte API Economy momentan Konjunktur?

Arne Limburg: Das hat mehrere Aspekte. Da ist zum Einen der technologische Aspekt. Moderne REST-APIs sind viel schlanker als frühere Lösungen wie SOAP oder XML-RPC und das sowohl im Design als auch bei der Datenmenge, die über die Leitung geht.

Dann gibt es natürlich den architektonischen Aspekt. Man hat mittlerweile erkannt, dass Architekturen, die auf einem ESB basieren, nicht so leicht zu warten und weiterzuentwickeln sind, als wenn sich die Services direkt unterhalten. Damit das gelingt, benötigt man aber gute APIs, die auch stabil bleiben. Da spielt das Thema Abwärtskompatibilität und Versionierung eine wichtige Rolle.

Und last but not least haben viele Unternehmen erkannt, dass sich mit gut definierten Public APIs auch Geld verdienen lässt. Unternehmen erschließen sich neue Vertriebskanäle, in dem sie APIs anbieten, über die z.B. Mobile Clients angebunden werden können (Stichwort: Multi-Channel-Strategie). Die Clients müssen dann nicht immer vom Unternehmen selbst gebaut werden. Es gibt auch interessante Konstellationen, in denen Drittanbieter an dem Unternehmensumsatz partizipieren können.

W-JAX: Deine Session auf der W-JAX heißt „Abwärtskompatible APIs – Strategien für den Projektalltag.“ Dabei gehst du darauf ein, wie man Schnittstellen sinnvoll weiterentwickelt, wenn sich beispielsweise die Anforderungen verändert haben. Warum genügt es nicht einfach, eine Versionierung für APIs einzuführen?

Arne Limburg: Es geht vor allem um die Pflege alter Versionen. Wenn ich ein Public API betreibe, kann ich nicht einfach eine Version 2 des API zur Verfügung stellen und Version 1 abschalten. Damit erzeuge ich hohen Aufwand bei meinen Clients, die dann zeitgleich updaten müssten. Auf Dauer macht das kein externer Client-Anbieter mit.

Die Erfahrung zeigt aber, dass die Pflege alter Versionen serverseitig sehr aufwendig ist, wenn man es nicht richtig angeht. Es geht also darum, einen Weg zu finden, serverseitig alte Versionen einfach pflegen zu können und gleichzeitig den Clients leichte Wege zu eröffnen, auf die neueste Version zu aktualisieren.

W-JAX: Kannst du einmal einen Tipp geben, wie man Abwärtskompatibilität von APIs sicherstellen kann, ohne sich im Support alter Versionen zu verlieren?

Arne Limburg: In aller Ausführlichkeit erkläre ich das natürlich in meinem Talk auf der W-JAX. Kurz gesagt, geht es darum, einerseits gewisse Anforderungen an den Client zu stellen (Stichwort: Tolerant Reader Pattern) aber andererseits auf dem Server auch dafür zu sorgen, dass die API kompatibel bleibt, in dem innerhalb einer Version nur Attribute hinzukommen, aber niemals welche entfernt werden. Beim Versionssprung ist es wichtig, dass das Mapping zwischen alter und neuer Version nicht zu aufwendig ist.

 

 

W-JAX: In den meisten Fällen haben Unternehmen noch Legacy-Systeme am Laufen, die bei der Einführung von APIs integriert werden müssen. Welche technologischen Herausforderungen gilt es dabei zu meistern?

Arne Limburg: Hier gibt es zwei Arten von Legacy-Systemen. Für die einen ist das Unternehmen im Besitz des Source Codes und hat auch das Know-How, um die Systeme weiterzuentwickeln. Hier empfehlen wir immer, ein sauberes RESTful API für das Legacy-System zu realisieren, um es in die „neue Welt“ einzubinden. Häufig ist das gar nicht so schwer.

Sollte sich das nicht realisieren lassen, empfehlen wir einen Anti-Corruption-Layer, der dann die saubere Schnittstelle zur Verfügung stellt und eigentlich nichts anderes macht als zwischen Legacy und neuer Welt hin- und herzumappen. Das kann dann z.B. auch ein Caching beinhalten, wenn das Legacy-System nicht auf eine so hohe Anzahl von Requests ausgelegt ist oder wenn es sogar nur im Batch-Betrieb läuft.

W-JAX: Bei der Einführung von APIs bleibt es aber ja nicht bei den technologischen Herausforderungen. Weshalb hat das auch Konsequenzen auf die gesamte Organisation eines Unternehmens?

Arne Limburg: In vielen Unternehmen ist es nach wie vor so, dass die Entwicklung sehr Projekt-getrieben ist. Die Einführung eines neuen Features ist ein eigenes Projekt, für das es ein separates Budget gibt und häufig auch noch ein Plichten- und Lastenheft.

Moderne APIs müssen allerdings als Produkt betrieben werden, das kontinuierlich weiterentwickelt wird und auf diese Weise benötigte Features zur Verfügung stellt. Die Art und Weise, wie neue Themen in die IT eingebracht werden, muss sich daher häufig komplett ändern.

W-JAX: Wie sollte man ein API-basiertes Projekt deiner Erfahrung nach angehen? Es gibt da ja verschiedenste Ansätze: Startet man technologisch, oder muss man zuerst das Unternehmen umstrukturieren? Braucht es zunächst ein ausgefeiltes Konzept zum API Management, oder ist der MVP-Ansatz hier besser: erstmal klein starten, Feedback einholen, weiterentwickeln?
Arne Limburg: Das Vorgehen, erst einmal klein anzufangen, um Erfahrung zu sammeln, ist auf jeden Fall ein Vorgehen, das sich bewährt hat. Dennoch sollte man sich beim Design einer API nicht von der Technologie treiben lassen. Es geht ja nicht primär darum, wie ich den Server schnell realisieren kann, sondern darum, eine API so aufzubauen, dass viele unbekannte Clients sie leicht nutzen können und gerne nutzen. Mein Ansatz ist deshalb immer der sogenannte Contract-First-Ansatz, wobei der Name etwas irreführend ist, weil er nicht das Ziel widerspiegelt. Eigentlich müsste man den Ansatz Client-First nennen. Gute APIs sind in der Regel die, bei denen das Design mit der Überlegung ausgeführt wurde: Was benötigt der Client?

W-JAX: Vielen Dank für dieses Interview!

 

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

 

 

The post Was Sie bei der Einführung von APIs wissen sollten appeared first on JAX.

]]>
Microservices sind kein Allheilmittel! https://jax.de/blog/software-architecture-design/microservices-sind-kein-allheilmittel/ Tue, 11 Sep 2018 09:16:40 +0000 https://jax.de/?p=65214 In Zeiten von Agile, DevOps und DDD verändert sich auch die Rolle des Software-Architekten. Wir haben uns mit Ralf D. Müller, darüber unterhalten, wie man als Software-Architekt den richtigen Mix aus Stabilität und Flexibilität findet, welche Impulse von der DevOps-Bewegung ausgehen und wie DDD dabei hilft, wertschöpfende Software zu bauen.

The post Microservices sind kein Allheilmittel! appeared first on JAX.

]]>
W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

Ralf D. Müller: Beide Aspekte der Architektur – Stabilität und Flexibilität – müssen wie immer ausgewogen vorhanden sein und bauen aufeinander auf. Erst wenn gewisse Standards existieren und die Vorgehensweise, Schnittstellen etc. dokumentiert sind, lässt sich eine Architektur auch gut ändern, ohne die Stabilität zu riskieren. Deshalb ist es ja auch so wichtig, dass die Architektur gut kommuniziert und die Pfade zur Umsetzung der architekturellen Aspekte ausgetrampelt werden.

Nur wenn jeder im Team weiß, auf was es architekturell ankommt, entsteht die benötigte Stabilität, um später flexibel auf geänderte Anforderungen reagieren zu können. Das arc42-Template von Gernot Starke und Peter Hruschka hilft hier bei der Strukturierung der Dokumentation.

W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden? Hast du da vielleicht einen Tipp aus deinen langjährigen Erfahrungen?

Ralf D. Müller: Jedes Projekt ist anders und bringt unterschiedliche Anforderungen bezüglich Stabilität und Flexibilität mit. Deswegen ist es wichtig, einen Blick auf die Requirements zu werfen und nicht einfach eine interessante Architektur eines anderen Projekts zu übernehmen. Die Requirements geben meist vor, welcher Teil der Architektur flexibel und welcher stabil sein muss.

Soll z.B. ein White-Label Produkt erstellt werden, dann ist das Design sicherlich flexibler zu halten als bei einer Anwendung zur internen Verwendung. Aber die beiden Attribute müssen sich auch nicht widersprechen: Erst die Stabilität in den Schnittstellen zwischen wohldefinierten Modulen ermöglicht die Flexibilität zur Änderung einzelner Module.

 

W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

Ralf D. Müller: Hat man das früher gemacht – Anwendungen einfach über den Zaun hin zum Ops-Team geworfen?  Der Betrieb der Software war schon immer ein wichtiger Aspekt der Architektur. Eine Applikation wird meist länger betrieben, als entwickelt. Somit ist der Aspekt des Betriebs für den Erfolg der Software mindestens genauso wichtig wie z.B. der Aspekt des Clean Code.

Aus meiner Sicht hat sich in diesem Bereich die wichtigste Änderung nicht direkt durch DevOps ergeben, sondern durch die iterativen Entwicklungszyklen eines agilen Projekts. Es gibt nicht mehr das Upfront-Design der Architektur, sondern man kann ein Projekt nun über mehrere Release-Zyklen begleiten. Dadurch sieht der Architekt vor allem, wie die Architektur die Qualitätskriterien des Projekts auch tatsächlich implementiert und kann die Architektur entsprechend anpassen.

W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

Ralf D. Müller: Es stimmt schon, dass Software-Architektur streckenweise mehr mit Management als mit Technologie zu tun hat. Aber wie bei allem hängt es sehr stark vom eigentlichen Projekt und des Typs „Architekt“ ab, den man verkörpert. Mich selbst würde ich weniger als Projekt-, sondern mehr als Architekturmanager sehen. Die Architektur, die in meinem Kopf ist, muss irgendwie raus in die Umsetzung. Das geschieht durch Dokumentation, Kommunikation und auch Management.

 

 

W-JAX: Auf der W-JAX hältst du einen Talk namens „Docs-as-Code“. Wo liegt der große Unterschied zwischen dem Docs-as-Code-Ansatz, den ihr beschreibt, und der traditionellen Art und Weise, Software zu dokumentieren?

Ralf D. Müller: Der Unterschied ist recht groß und vielfältig. Ich denke, jeder kennt die klassische, mit einer Textverarbeitung erstellte Dokumentation, die getrennt vom Code verwaltet wird. Dokumentation gehört aber, wie Tests auch, zum Code und sollte mit diesem verwaltet werden. Dadurch ist immer klar, wo die aktuelle Version liegt.

Sobald die Dokumentation zusammen mit dem Code verwaltet wird, können auch weitere Aspekte der Softwareentwicklung auf die Dokumentation übertragen werden. So kann z.B. das Aktualisieren von Diagrammen automatisiert im Build umgesetzt und die Dokumentation sogar automatisiert getestet werden. Wird eine Änderung am Code vorgenommen, so gehört es mittlerweile zur Definition of Done, auch die Tests anzupassen. Mit Docs-as-Code wird im gleichen Schritt auch die Dokumentation gepflegt, weil ein Pull-Request sonst nicht als vollständig im Sinne der DoD angesehen wird.

W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?

Ralf D. Müller: Ich beobachte momentan, wie der Ansatz der Microservices reift. Zum Einen setzt sich die Erkenntnis durch, dass auch Microservices nicht die Lösung für jedes Problem sind und man abwägen muss. Aber auch die Art der Implementierung von Microservices auf der JVM entwickelt sich weiter. So steht mit micronaut.io mittlerweile ein Framework zur Verfügung, welches zielgerichtet auf Microservices hin entwickelt wurde und nicht als Full-Stack Framework entstand. Auch der Serverless-Ansatz ist in diesem Zusammenhang spannend. Solche Entwicklungen sorgen dafür, dass die Arbeit als Software-Architekt immer spannend bleiben wird.

Vielen Dank für dieses Interview!

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

The post Microservices sind kein Allheilmittel! appeared first on JAX.

]]>
Wie werde ich ein erfolgreicher Software-Architekt? https://jax.de/blog/software-architecture-design/wie-werde-ich-ein-erfolgreicher-software-architekt-blog/ Mon, 03 Sep 2018 09:26:28 +0000 https://jax.de/?p=65170 In Zeiten von Agile, DevOps und DDD verändert sich auch die Rolle des Software-Architekten. Wir haben uns mit Eberhard Wolff, darüber unterhalten, wie man als Software-Architekt den richtigen Mix aus Stabilität und Flexibilität findet, welche Impulse von der DevOps-Bewegung ausgehen und wie DDD dabei hilft, wertschöpfende Software zu bauen.

The post Wie werde ich ein erfolgreicher Software-Architekt? appeared first on JAX.

]]>
W-JAX: Software-Architektur galt lange als die Disziplin, in Software-Projekten für einen kohärenten Zusammenhang zu sorgen: Es geht darum, Stabilität und Langlebigkeit zu gewährleisten, Standards einzuführen, für Sicherheit zu sorgen, Pläne und Dokumentationen zu erstellen, etc. Heute wird Software-Architektur oft auch anders diskutiert, und zwar im Sinne eines Change Management: Architekturen sollen flexibel, erweiterbar, austauschbar sein. Wie siehst du dich: Wie viel in deiner Arbeit ist Kirchenbauer, wie viel Change Manager?

Eberhard Wolff: Stabilität und Standards sind keine Ziele, sondern Mittel, um ein wartbares System zu erstellen. Wenn die Software einheitlich gestaltet ist, können Entwickler sich leichter einarbeiten und die Systeme einfacher ändern. Also ist dieses Vorgehen nur dazu da, um Änderbarkeit zu erreichen. Aber dieser Ansatz funktioniert nur theoretisch.

In der Praxis verlieren große Systeme mit der Zeit ihre Struktur und ihre Einheitlichkeit. So werden sie immer schwerer wartbar, was die Langlebigkeit begrenzt. Daher setzen aktuelle Ansätze darauf, Komponenten ersetzbar zu machen, um so der mangelnden Langlebigkeit zu entgehen. Das bieten Microservices. Sie können ersetzt werden, und zum Ersetzen können auch neue Technologien genutzt werden. Ebenso ist es möglich, Microservices mit unterschiedlichen Technologien zu implementieren und so mit der Vielfalt besser umzugehen.

W-JAX: Wie schafft man es, den richtigen Mix aus Stabilität und Flexibilität zu finden? Hast du da vielleicht einen Tipp aus deinen langjährigen Erfahrungen?

Eberhard Wolff: Meiner Meinung nach liegt das Problem auf einer anderen Ebene: Stabilität und Flexibilität sind nur unterschiedliche Wege, um das Ziel Wartbarkeit zu erreichen. Meistens treffe ich in Projekten auf das Problem, dass die Ziele des Projektes unklar sind oder nicht in der Architektur abgebildet worden sind.

Sicher ist Wartbarkeit wichtig, damit man auch in der Zukunft noch das System anpassen kann. Aber wenn das System gar nicht in Produktion geht, weil es Compliance-Richtlinien nicht einhält, die notwendige Performance nicht erreicht oder die Vorgaben im Betrieb nicht erfüllt, hilft die Wartbarkeit nichts. Und wenn das System in den Betrieb geht, aber kein sinnvolles Geschäftsziel unterstützt, ist das System ebenfalls sinnlos.

Architektur bedeutet, eine technische Lösung für ein Problem zu finden. Das wiederum bedeutet, das Problem zu kennen. Zu oft wird einfach nur Wartbarkeit angestrebt – ohne die wirklichen Probleme überhaupt zu lösen oder zu analysieren.

 

W-JAX: Im Zuge der DevOps-Bewegung erweitert sich das Bild des Software-Architekten noch um eine weitere Facette: Es geht nämlich nicht nur um Anwendungsentwicklung, sondern immer mehr auch darum, wie sich Anwendungen in einer Continuous-Delivery-Landschaft einbetten. „You build it, you run it“ heißt da das Stichwort. Wie hat die DevOps-Bewegung die Rolle des Software-Architekten verändert? Was musst du als Architekt heute anders machen, als früher, als man die Anwendungen noch einfach über den Zaun hin zum Ops-Team geworfen hat?

Eberhard Wolff: Eigentlich geht es immer noch um dasselbe: Ein System ist für Anwender nur nützlich, wenn es in Produktion ist. Das ist mittlerweile durch Continuous Delivery und DevOps offensichtlich. Daher sollte der Architekt auch diesen Aspekt betrachten. Das klassische Ziel der einfachen Änderbarkeit kann durch Continuous Delivery ebenfalls besser erreicht werden: Software, die schneller und einfacher in Produktion gebracht werden kann, ist einfacher änderbar, weil Änderungen schneller dahin gebracht werden, wo es zählt: In die Hände der Nutzer und zwar abgesichert durch Tests.

Auf der anderen Seite ist die einfache Betreibbarkeit einer Anwendung eine Voraussetzung für eine möglichst einfache Continuous-Delivery-Pipeline. Das kann die Auswahl der Technologien einschränken oder dazu führen, dass Ops-Anforderungen wie Monitoring oder Logging schon frühzeitig betrachtet werden. Das verringert das Risiko und macht allen – Betrieb und Entwicklung – das Leben einfacher.

W-JAX: Ein weiterer Trend ist aktuell, das Design einer Software stark an den fachlichen Domänen auszurichten. Neben DDD als Theorie erobern gerade Microservices-Architekturen die Praxis. Neben den technologischen Aspekten, die Domänen-fokussierte Anwendungen mit sich bringen, geht es hier zentral auch darum, die beteiligten Leute erst einmal in ein Boot zu holen: Fachexperten, Entwickler und natürlich auch die Geschäftsleitung und Anwender bzw. Kunden. Ist man da als Software-Architekt nicht eigentlich zu 80% Projektmanager? Wie hältst du das persönlich: Wie stark nimmst du die Rolle des Projektmanagers ein, wie viel konzentrierst du dich auf Technologien?

Eberhard Wolff: Fachliche Anforderungen zu verstehen ist zentral, um die richtigen Probleme zu lösen. Außerdem ist Architektur eigentlich die Strukturierung der Fachlichkeit. Das geht nur mit fachlichem Wissen und dem Austausch mit fachlichen Experten. Das ist aber kein Projektmanagement und auch nichts Neues. Domain-driven Design ist auch schon fast 15 Jahre alt. Am Ende sollte der Architekt wie alle anderen auch seinen Teil dazu beitragen, dass das Projekt erfolgreich ist. Dazu ist die Fachlichkeit und ihre Strukturierung meist wichtiger als die Technik.

 
 

 

W-JAX: Auf der W-JAX hältst du einen Talk namens „Wie werde ich ein erfolgreicher Softwarearchitekt?“ Dabei gehst du auf Voraussetzungen für einen guten Softwarearchitekten ein, auf die man zunächst vielleicht nicht gleich kommt. Kannst du da einmal ein Beispiel nennen?

Eberhard Wolff: Softwarearchitekten arbeiten zwar mit technischen Herausforderungen, aber zentral ist die gemeinsame Arbeit an den zu lösenden Problemen. Daher geht es in dem Vortrag vor allem darum, wie Architekten ihre Rolle leben sollten, ihr Wissen so einbringen können, dass es auch wirklich umgesetzt wird, und wie man auch das Wissen der anderen Team-Mitglieder nutzbar macht. Aber es gibt natürlich auch ein paar ganz praktische Tipps.

W-JAX: Welchen Trend findest du im Bereich der Software-Architektur momentan besonders spannend – und warum?

Eberhard Wolff: Der nächste Trend sollte sein, sich mit den Zielen und Herausforderungen des jeweiligen Projekts auseinanderzusetzen und dafür passende technische Lösungen zu finden. Andere Trends können sicher helfen, um neue Lösungsmöglichkeiten kennen zu lernen. Es ist gut, wenn man sie unvoreingenommen für die passenden Szenarien nutzt. Aber ich sehe zu oft Architekturen, die dem letzten Trend entsprechen, aber keiner kann sagen, welche Ziele damit wie erreicht werden sollen. Das finde ich schade, denn die Aufgabe eines Architekten ist eben, eine technische Lösung zum Erreichen der Ziele des Projekts zu finden.

 

Erfahren Sie mehr über Software Architecture auf der W-JAX 2018:


● Integration Patterns for Microservices
● Wie werde ich ein erfolgreicher Softwarearchitekt?

 

 

Cheat-Sheet: Die neuen JEPs im JDK 12


Unser Cheat-Sheet definiert für Sie, wie die neuen Features in Java 12 funktionieren. Von JEP 189 „Shenandoah“ bis JEP 346 „Promptly Return Unused Committed Memory from G1“ fassen wir für Sie zusammen, was sich genau ändern wird!

Cheat-Sheet sichern!

The post Wie werde ich ein erfolgreicher Software-Architekt? appeared first on JAX.

]]>
Jenkins Tutorial: So baut man einen Jenkins-Cluster https://jax.de/blog/software-architecture-design/jenkins-cluster-verteilung-eines-deployment-servers/ Thu, 28 Jun 2018 12:11:07 +0000 https://jax.de/?p=64220 Sobald der Deployment-Prozess mit Jenkins mehrere Stufen annimmt und zusätzlich noch automatisierte Tests in größeren Projekten dazukommen, muss man sich mit dem Thema Skalierung auseinandersetzen. Erschwerend kann hinzukommen, dass mehrere Teams mit Jenkins arbeiten und die fertigen Applikationen für mehrere Kunden in unterschiedlichen produktiven Umgebung bereitstellen sollen. Eine Möglichkeit, Jenkins zu skalieren, ist der Aufbau eines Jenkins-Clusters.

The post Jenkins Tutorial: So baut man einen Jenkins-Cluster appeared first on JAX.

]]>

von Jörg Jackisch

Sobald wir mit mehreren Teams arbeiten und mehrere Projekte abwickeln und bereitstellen, müssen wir an die Skalierung von Jenkins denken. Dies lässt sich aber mit einer Standardinstallation von Jenkins nur sehr schwer umsetzen, wenn überhaupt. Denn die einzelnen Build-Prozesse auf dem überforderten Jenkins-Server nehmen immer mehr Zeit in Anspruch, was bei der produktiven Zeit der Entwickler und Tester verloren geht. Bei weiter steigender Last auf dem Server wird dieser außerdem zunehmend instabil und fällt öfter aus. Der Frust sowohl bei den Entwickler- als auch Testteams und Teamleitern bis hin zu den Managern ist dann groß. Dieser Frust durch instabile Infrastrukturkomponenten schlägt sich schließlich auch in der Produktivität und Qualität des gesamten Projekts nieder. Das wollen wir natürlich mit allen Mitteln vermeiden. Also muss Skalierung her.

Jenkins skalieren – wie geht das?

Jenkins lässt sich sowohl horizontal als auch vertikal skalieren. In der vertikalen Skalierung kann man den Server mit mehr Hardwareressourcen ausstatten, damit die Jenkins-Applikation performanter ist. Dazu gibt es mehrere Möglichkeiten. Die einfachste ist, die Anzahl der Prozessoren und den Arbeitsspeicher zu erweitern. Auch die I/O-Performance kann man verbessern, indem man bei den Speichermedien zu SSD wechselt und die unterschiedlichen Komponenten mit Fibre Channel verbindet, wenn das Serversystem es zulässt. Bei der vertikalen Skalierung stößt man aber häufig auf technische Grenzen. Hinzu kommt, dass auch die eingesetzten Werkzeuge und Programme, die innerhalb von Jenkins genutzt werden, Multithreading unterstützen müssen, sonst bringt Skalierbarkeit im vertikalen Sektor nicht den gewünschten Effekt. Außerdem bringt das Aufrüsten der Serverhardware keine Ausfallsicherheit mit.

Lesen Sie auch: Grundkurs Microservices: Warum Frameworks nicht genug sind

Da die vertikale Skalierung an Grenzen stößt und man damit keine nachhaltige und langfristige Steigerung erzielen kann, sollte man rechtzeitig die horizontale Skalierbarkeit prüfen und umsetzen. Die horizontale Skalierung beschreibt dabei das Clustering der Anwendung. Jenkins setzt dabei auf eine Master-Agent-(Slave-)Skalierung. Ziel ist es, die Anwendung auf viele Server zu verteilen. Dabei gibt es zwei Alternativen: Zum einen spricht man von einem High-Performance-Computing-(HPC-)Cluster, das dazu dient, die Rechenkapazität zu erhöhen. Zum anderen gibt es High-Available-(HA-)Cluster, die größeren Wert auf die Ausfallsicherheit des Systems legen. Man unterscheidet grundsätzlich zwischen Hardware- und Software-Clustern. In beiden Kategorien gibt es unterschiedliche Methoden der Umsetzung.

 

Das Jenkins-Cluster in der Theorie

Bei der Einführung eines Jenkins-Clusters beginnt man mit der Einrichtung und Installation der Stand-alone-Variante von Jenkins. Er bildet die Basis des Clusters, verwaltet die gesamte Build-Umgebung und führt über seinen eigenen Executor die Build-Prozesse aus. Der erste Schritt ist es, den Jenkins-Server vertikal zu skalieren, zusätzlich passt man die JRE an. Damit wird man vorübergehend wieder eine stabile Infrastruktur erreichen.

Verschaffen Sie sich den Zugang zur Java-Welt mit unserem kostenlosen Newsletter!

In der Regel ist es allerdings nicht zu empfehlen, die Stand-alone-Variante in einer großen und produktiven Umgebung einzusetzen. Das sollte nur der erste Schritt bei der Einführung von Jenkins sein, denn hier kommt ein großes Sicherheitsrisiko hinzu. Eine Webapplikation führt die Prozesse mit dem Benutzer aus, mit dem der Jenkins-Server gestartet wurde. Meistens ist das ein Benutzer, der erhöhte Rechte besitzt. Das bedeutet, dass der Benutzer der Webapplikation Zugriff auf alle Ressourcen besitzt. Wenn ein Angreifer also Schadcode einschleusen kann, hat er Zugriff auf private oder geheime Daten.

Um vom einzelnen Server zu einer verteilten Lösung zu kommen, bietet Jenkins die Möglichkeit, mehrere sogenannte Worker-Prozesse auf unterschiedliche Server zu verteilen. Dabei bleibt der erste einzelne Server der Master und die weiteren dienen als sogenannte Agents. Dieses Prinzip nennt man Master/Agent Clustering. Dabei verwaltet der Masterserver die komplette Umgebung. Er verteilt einerseits die Deployment-Jobs, andererseits überwacht er, ob die jeweiligen Agents noch verfügbar sind. Der Master dient lediglich dazu, Informationen zu sammeln, Prozesse zu verwalten und letztendlich als grafische Oberfläche des gesamten Schwarms. Die Agents oder Worker sind Server, die nur vom Master Build-Prozesse entgegennehmen und bearbeiten.
Die Master/Agent-Methode gehört zu den HPC-Cluster-Methoden. Die Verteilung der Build-Jobs wird dabei auf alle vorhandenen Worker-Prozesse ausgelagert. Die Verteilung der Jobs lässt sich allerdings auch so konfigurieren, dass bestimmte Abläufe und Prozesse nur auf bestimmten Agents ausgeführt werden.

Damit der Master mit seinen Agents kommunizieren und nachvollziehen kann, ob sie noch verfügbar sind, kann man drei unterschiedliche bidirektionale Kommunikationsmethoden verwenden: Kommunikation per SSH (Secure Shell), JNLP-TCP (JNLP, Java Native Launch Protocol) oder JNLP-HTTP (Java Webstart). Falls es über keinen der vorhandenen Konnektoren möglich ist, eine Verbindung zwischen den Agents und dem Master aufzubauen, kann man auch ein eigenes Skript entwickeln. Als Programmiersprachen für eigene Konnektoren eignen sich vor allem Groovy oder Grails, doch auch Implementierungen mit der Programmiersprache Python sind gängig.

Bei der Methode mit dem SSH-Konnektor fungiert jeder Agent als SSH-Server und der Jenkins-Master ist der SSH-Client; in der Regel via Port 22 mithilfe eines SSH-Schlüssels, der auf dem Agent erstellt wird. Man kann die Verbindung aber auch ohne Schlüssel aufbauen. Dann wird sich der Server wie bei einer gewöhnlichen SSH-Kommunikation mit einem Benutzernamen und einem Passwort anmelden. Es ist empfehlenswert, den Benutzer zu nehmen, mit dem auch der Jenkins-Master läuft. Agents, die auf Microsoft Windows basieren, lassen sich mithilfe des Programms Cygwin verbinden. Cygwin [1] ist eine Sammlung von Open-Source-Werkzeugen, die unter Windows Funktionalitäten eines Linux-Systems bereitstellen.

Jenkins bietet mit dem JNLP-HTTP- und dem JNLP-TCP-Konnektor zwei Varianten, mit denen man mithilfe der Java-internen Protokolle Master und Agent miteinander kommunizieren lassen kann. Um JNLP zu verwenden, arbeitet Jenkins mit der Java-Web-Start-Technologie. Das ist wahrscheinlich die einfachste Art und Weise, Agent und Master zu verknüpfen, da man auf dem Agent lediglich die Java-Applikation des Masters ausführen muss und so bereits die Verbindung aufgebaut hat.

 

Der Software Architecture Track auf der JAX 2020

 

Das Jenkins-Cluster in der Praxis

Nach der ganzen Theorie erstellen wir jetzt ganz praktisch mit Docker einen Jenkins-Schwarm. Bei diesem Beispielszenario verbinden wir auf unterschiedliche Art und Weise vier Agents mit einem Masterserver. Es gibt also insgesamt fünf Server. Der Masterserver basiert auf einem Debian Linux, ebenso wie drei der Agents. Zusätzlich wird ein Server hinzugenommen, auf dem Windows läuft. Auf dem Masterknoten des Jenkins-Schwarms läuft das Jenkins Backend. Eine solch heterogene Verteilung bringt den Vorteil, dass man bestimmte Schritte innerhalb des Deployment-Prozesses auf einem bestimmten Betriebssystem ausführen kann. Als Beispiel dienen hier automatisierte Browsertests: Ich möchte meine Java-EE-Applikation sowohl auf einem Windows-Betriebssystem mit Microsoft Edge als auch auf einem Linux-Betriebssystem mit Mozilla Firefox testen.

Basierend auf einem Debian Linux oder einer ähnlichen Linux-Distribution muss man folgende Befehle ausführen, um eine lauffähige Jenkins-Umgebung zu erstellen:

 

 $> apt-get update && apt-get upgrade

 

Mit apt-get update wird der Paketmanager APT anhand seiner Konfiguration aktualisiert und mit dem Kommando apt-get upgrade das Betriebssystem. Das sollte man stets vor einer Installation einer neuen Software durchführen. Mit dem Befehl $> apt-get install default-jre wget wird die JRE mit all ihren Abhängigkeiten installiert. Hinzu kommt auch das Programm Wget, mit dem Jenkins zur sources-list-Datei von APT hinzugefügt wird. Zum Hinzufügen der Sources für APT benutzt man die Zeilen aus der Dokumentation von Jenkins [2]:

 

$> wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | apt-key add -
$> sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$> apt-get update
$> apt-get install jenkins

 

Letztendlich wird mit dem letzten Kommando der Jenkins-Server installiert. Mit Docker kann man diese Installation abkürzen und virtualisiert den Server innerhalb eines Containers. Das Image, das dabei benutzt wird, befindet sich online im Docker Hub. Mit einem Befehl wird ein Container mit Linux erzeugt, auf dem ein Jenkins-Deployment-Server läuft:

 

$> docker run  --name=Master –link=slave3:2222 -d -ti -p 8080:8080 -p 50000:50000 jenkins:latest /bin/bash

 

Mit den Parametern –p werden die Ports 8080 und 50000 vom Container auf dieselben Ports auf dem Host gemappt. Docker lädt dabei jeden Image-Layer des Docker-Files von Jenkins einzeln herunter und richtet eine lauffähige Umgebung ein. Dabei wird die Bash auf dem Server gestartet, und man kann sich mit dem Befehl docker attach und der Container-ID mit der Bash des Servers verbinden. Zum Starten des Servers führt man den Befehl zum Starten von Jenkins aus: $> java -jar /usr/share/jenkins/jenkins.war &.

Nachdem der Jenkins-Masterserver gestartet ist, kann man in der Docker-Toolbox mit der Tastenkombination STRG + P + Q den Server wieder detachen.

Egal mit welcher Variante der Masterserver eingerichtet wurde, navigiert man im Browser seiner Wahl auf die jeweilige IP-Adresse des Servers. Bei Docker ist das die IP-Adresse des Docker Containers, auf dem der Jenkins-Server läuft, jeweils mit dem Port 8080. Unter http://IP_ADRESSE:8080/ wird jetzt der Set-up-Screen von Jenkins dargestellt. Dort muss man das initiale Passwort eingeben, das bei der Installation angezeigt wurde.

Falls man es übersehen hat, findet man es unter /var/jenkins_home/secrets/initialAdminPassword auf dem Server. Nach dem Set-up kann man sich beim Masterserver anmelden. Zu diesem Zeitpunkt hat man einen Stand-alone-Server eingerichtet, auf dem man die jeweiligen Projekte konfigurieren und einrichten kann.
Zum Erstellen der Slaveserver benutzt man wieder ein Docker Image, das ein Standard-Debian-Betriebssystem bereitstellt. Zum Starten der Server wird folgender Befehl ausgeführt:

 

docker run -ti --name=Slave1 -d debian:latest /bin/bash
docker run -ti --name=Slave2 -d debian:latest /bin/bash
docker run -ti --name=Slave3 –hostname=slave3 -p 2222:22 -d debian:latest /bin/bash

 

Dieser Befehl lädt das aktuelle Debian Image aus dem Docker Hub herunter und startet es als Daemon im Hintergrund (-d = detached). Mit dem Befehl docker ps –format “table {{.ID}}\t{{.Image}}\t{{.Names}}” sollten jetzt alle vier Linux-Server sichtbar sein.

Um die Linux Agents mit dem Masterserver zu verbinden, werden zuerst zwei der Linux-Server per JNLP verbunden. Einer wird per SSH die Verbindung mit dem Masterserver aufbauen. Im Jenkins-Master richtet man für die zwei weiteren Linux-Server jeweils einen weiteren Agent bzw. Knoten ein und lädt dann die Datei slave.jar auf den jeweiligen Slaveserver herunter. Die Knoten sollten sogenannte permanente Knoten sein, und als Startmethode wählt man LAUNCH AGENT VIA JAVA WEB START. Nach der Einrichtung der Agents wird dieser Befehl angezeigt:

 

Java -jar slave.jar -jnlpURL URL_DES_JENKINS_SERVERS -secret SECRET 

 

Als Parameter für die slave.jar werden die Verbindungsparameter benötigt. Der erste ist –jnlpURL, er teilt der slave.jar mit, wo sich der Server befindet. Der zweite ist ein Secret zur Verbindung (-secret). Den kompletten Befehl, um den Agent zum Master zu verbinden, sieht man, wenn man im Jenkins Backend durch die Punkte JENKINS VERWALTEN | KNOTEN VERWALTEN | SLAVE1 navigiert. Das Gleiche gilt für den zweiten Agent: JENKINS VERWALTEN | KNOTEN VERWALTEN | SLAVE2.

Der dritte Linux-Server kommuniziert per SSH mit dem Masterserver. Dazu geht man wieder mit dem Befehl docker attach in die Bash des Slaves und lädt als Erstes die slave.jar-Datei herunter. Ich benutze dafür Wget und verschiebe die Datei danach in das /bin-Verzeichnis. Zusätzlich muss man hier einen SSH-Server installieren, unter Debian Linux mit dem Befehl apt-get install openssh-server. Im nächsten Schritt legt man einen neuen Benutzer an und gibt ihm ein Passwort.

$> groupadd jenkins
$> useradd -g jenkins -d /home/jenkins -s /bin/bash jenkins
$> passwd jenkins

 

Nun erzeugt man einen SSH-Schlüssel, hinterlegt ihn auf dem Jenkins-Server und testet von ihm aus den Log-in.
Im Backend des Masterservers wird auch der dritte Knoten angelegt. Als Startmethode wird hier allerdings LAUNCH AGENT VIA EXECUTION OF COMMAND ON THE MASTER ausgewählt. Nach dieser Auswahl öffnet sich ein neues Konfigurationsfeld, in das man das Startkommando eintragen kann:

ssh -p 2222 [email protected] java -jar /bin/slave.jar

 

Alternativ bietet Jenkins auch die Startmethode STARTE SLAVE ÜBER SSH. Dort kann man den Hostnamen, Port, Benutzernamen und das Passwort eingeben. Der Jenkins-Server wird sich dann per SSH-Client auf den Server verbinden und ihn als Slave starten. In der Übersicht unter JENKINS VERWALTEN | KNOTEN sollten nun alle Agents dargestellt werden und online sein.

Zur Installation des Windows-Servers empfiehlt es sich, eine virtuelle Maschine zu benutzen. Dann folgt man den Installationsanweisungen des Set-ups von Microsoft. Man sollte allerdings bei der Auswahl der Version darauf achten, was man tatsächlich auf diesem Server ausführen möchte. In diesem Szenario empfiehlt sich Windows in der Clientversion 10, da man dort den Browser Edge zur Verfügung hat. Somit kann man seine Applikation auf Edge testen. Da man keine Serverversion des Betriebssystems benutzt, kann man auch weitere Versionen des Internet Explorers installieren.

Lesen Sie auch: Das Beste aus beiden Welten – Objektfunktionale Programmierung mit Vavr

Unter Windows benutzt man nahezu immer den Verbindungsaufbau per JNLP-HTTP, da dies eine sehr einfache Variante ist, Slaves mit dem Java Web Start zum Jenkins-Masterserver hinzuzufügen. Um den Windows-Server zum Schwarm als Agent hinzuzufügen, verbindet man sich per Remote Desktop zum Windows-Server. Auf diesem benutzt man einen Browser und navigiert zum Jenkins-Master. Nachdem man sich dort eingeloggt hat, klickt man in der Jenkins-Oberfläche auf den Menüpunkt JENKINS VERWALTEN | KNOTEN VERWALTEN und wählt den Menüpunkt NEUER KNOTEN, um einen neuen Agent hinzuzufügen. Im nächsten Fenster füllt man das Textfeld mit dem Namen seines Knotens aus, hier MS Windows Server, und wählt PERMANENT AGENT. Nach der Bestätigung kommt man zur Konfiguration des Knotens.

Das wichtigste Feld bei diesem Formular ist die Startmethode. Dort wählt man in unserem Beispiel LAUNCH AGENT VIA JAVA WEB START. Nach dem Speichern dieses Formulars kommt man zurück zur Übersicht der Knoten. Dort ist nun unser angelegter Windows-Knoten verfügbar, der allerdings mit einem roten Kreuz markiert ist, was bedeutet, dass der Agent nicht verbunden ist. Mit einem Klick auf den Agent kommt man zur Übersichtsseite, wo man mit Klick auf den Java-Webstart-Launch-Knopf die JNLP-Verdingung herstellen kann. Dazu wird eine slave-agent.jnlp-Datei heruntergeladen, die gestartet werden muss, und schon ist der Agent verbunden.

Quarkus-Spickzettel


Quarkus – das Supersonic Subatomic Java Framework. Wollen Sie zeitgemäße Anwendungen mit Quarkus entwickeln? In unserem brandaktuellen Quarkus-Spickzettel finden Sie alles, was Sie zum Loslegen brauchen.

 

Jetzt herunterladen!

 

Fazit

Zu Beginn eines Projekts sollte die Planung für den Deployment-Prozess und die Skalierung in der Architekturplanung enthalten sein. Man sieht, wie schnell und einfach man mit Docker einen Jenkins-Schwarm erstellen kann. Läuft der Cluster in einer produktiven Umgebung, kann langfristig und nachhaltig eine qualitativ hochwertige Software garantiert werden.

Links & Literatur
[1] Cygwin: https://www.cygwin.com
[2] Jenkins: https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+on+Ubuntu

 

The post Jenkins Tutorial: So baut man einen Jenkins-Cluster appeared first on JAX.

]]>
RESTful APIs richtig gemacht – Anleitung für bessere REST-Schnittstellen https://jax.de/blog/software-architecture-design/restful-apis-richtig-gemacht/ Mon, 05 Mar 2018 11:04:47 +0000 https://jax.de/?p=62830 Wer schon einmal eine Domäne mit Microservices aufgebaut hat, wird es bereits wissen: APIs für die Service-zu-Service-Kommunikation sind von zentraler Bedeutung. Da jedes Team seinen eigenen Stil hat und Schnittstellen jeweils anders implementiert, kommt es über kurz oder lang zu einem Wildwuchs von verschiedenen Ansätzen. Gleich zu Projektbeginn einen Leitfaden mit Richtlinien und Beispielen zu definieren, hilft, einheitliche und möglichst selbsterklärende APIs zu gewährleisten.

The post RESTful APIs richtig gemacht – Anleitung für bessere REST-Schnittstellen appeared first on JAX.

]]>
von Thomas Spiegl und Manfred Geiler
REST mit JSON ist der heute am weitesten verbreitete Ansatz für neue Programmierschnittstellen. Unzählige Bücher, Vorträge, Blogs und andere Quellen im Internet beschäftigen sich mit dem Thema. Trotzdem scheint es große Auffassungsunterschiede in der Entwicklergemeinde zu geben, wie Webschnittstellen auszusehen haben. Die JSON-API-Spezifikation [1] legt genau fest, wie ein RESTful API basierend auf einem einheitlichen Standard implementiert werden sollte.
Zunächst aber noch ein paar Worte zu APIs. Bei der Definition eines API sollte sich der Entwickler genug Zeit nehmen, denn gut entworfene APIs führen automatisch zu besseren Produkten. Es empfiehlt sich, zu Beginn eines Projekts Richtlinien zu entwickeln, die die zentralen Anforderungen an ein API festhalten. Mithilfe von Schlüsselwörtern laut RFC 2119 [2] lässt sich festlegen, wie wichtig (oder zwingend) eine einzelne Anforderung ist. Themen für einen Richtlinienkatalog sind beispielsweise API-Namensgebung, HTTP Header und Operationen, Anfrageparameter oder Dokumentstruktur.
Richtlinien für den API-Entwurf helfen, ein gemeinsames Verständnis zu entwickeln. Grundsätzlich lässt sich sagen, dass Services, die einheitliche Standards verfolgen, leichter zu verstehen und einfacher zu integrieren sind. Daneben lassen sie sich effizienter umsetzen (gemeinsame Tools), sind stabiler gegenüber Änderungen und einfacher zu warten.

RESTful Styleguide für JSON-APIs

In der JSON-API-Spezifikation [1] finden sich konkrete Vorschläge, wie ein gut entworfenes REST API aussehen kann. Aus unserer Sicht ist die Spezifikation ein guter Einstiegspunkt ins Thema. Für unsere Beispiele begeben wir uns gedanklich in die Welt der Sportvereine. Wir wollen mit unserer gedachten IT-Lösung Teams, Manager und Spieler verwalten.

 


Abb. 1: Datenmodell

 

Das Diagramm (Abb. 1) zeigt die Entität Team und seine Beziehungen. Ein Team wird von einem Manager trainiert. Einem Team werden mehrere Spieler (Player) zugeordnet. Manager und Player sind jeweils vom Typ Person.

 

Manager oder Coach?

Die Begriffe Manager und Head Coach werden im englischen Fußball synonym für den Cheftrainer einer Mannschaft verwendet. Zum Zwecke der Lesbarkeit verwenden wir hier den Begriff Manager.

 

Das API: Mit wem dürfen wir sprechen?

Den Einstieg ins API bietet für unsere Beispielapplikation die zentrale Domain Entity Team. Über einen API-URL-Pfad werden klassische Operationen wie Lesen, Schreiben, Ändern, Löschen oder die Verwaltung von Beziehungen ermöglicht. Der Pfad dient auch der Dokumentation für den API-Anwender und sollte daher klar und einfach zu deuten sein. Als Einstiegspunkt für die Ressource Team bietet sich der Pfad /teams an. Der Pfadname ist in der Mehrzahl angegeben, schließlich sollen ja mehrere Teams gleichzeitig verwaltet werden können.
Wir legen daher im API fest, dass eine Entität über den Ressourcenpfad /{entity-type} verwaltet werden kann. Es handelt sich dabei typischerweise um eine Collection (1 bis n Teams). Als Namenskonvention gilt: ein Entity Type ist (oder endet auf) ein Nomen in der Mehrzahl, enthält nur Kleinbuchstaben, und einzelne Worte werden durch ein Minus getrennt:

 

richtig: /teams, /team-memberships

falsch: /Teams, /team, /teamMemberships, /team-search

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Datenobjekte abfragen: Welche Mannschaften gibt es eigentlich?

Beginnen wir mit einem einfachen HTTP GET auf den API-Pfad /teams:

GET /teams HTTP/1.1Accept: application/vnd.api+json

Im HTTP Header Accept mit dem Wert application/vnd.api+json wird festgelegt, dass in der Antwort ein JSON-Dokument erwartet wird. Jeder HTTP Request sollte diesen Header setzen. Das zurückgelieferte Dokument enthält ein Array aller Team-Objekte. Eine Response könnte also etwa so aussehen wie in Listing 1.

Listing 1: Array aller „Team“-Objekte


{

   "data": [

       {

           "id": "1",

           "type": "teams",

             "attributes": {  

                 "name": "FC Norden Jugend",  

                 "category": "juniors"  

             },  

             "links": {  

                 "self": "http://example.com/teams/1"  

             }  

         },  

         {  

             "id": "2",  

             "type": "teams",  

             "attributes": {  

                 "name": "FC Essen",  

                 "category:" "masters"  

             },  

             "links": {  

                 "self": "http://example.com/teams/2"  

               }

       }

   ]

}

Die Dokumentstruktur: Ordnung ist das halbe Leben

Zu einem gut strukturierten API gehört auch eine vorgegebene Dokumentstruktur. Sie hilft, wiederverwendbare Werkzeuge zu bauen, auch um das Zusammenspiel von Komponenten und den Daten im User Interface zu erleichtern. Ein API mit einheitlicher Struktur wirkt wie aus einem Guss – auch dann, wenn unterschiedliche Entwicklerteams an seiner Definition arbeiten. Die Struktur eines JSON-Dokuments wird von der JSON API Specification [3] genau festgelegt. Jedes Dokument besitzt immer eines von zwei Elementen: data oder errors. Optional können auch die Elemente meta, jsonapi, links oder included auf oberster Ebene enthalten sein. Das Element data enthält die eigentlichen Daten und kann entweder aus einem einzelnen Resource-Objekt oder aus einem Array von Resource-Objekten bestehen. Zusätzlich können Objektreferenzen oder auch null enthalten sein. Im Dokument aus dem letzten Beispiel wird im data-Element ein Ressource-Array, eine Liste von Datenobjekten vom Typ teams, geliefert. Das Datenobjekt wiederum hat die Elemente id, type, attributes und links. Die Attribute id und type stellen eine Referenz {“id”: “1”, “type”: “teams”} auf eine Entität vom Typ teams dar. Jede Ressource muss diese beiden Attribute besitzen. Datenobjekte bleiben so auch losgelöst vom API noch eindeutig identifizierbar. Der Typ deckt sich mit dem Pfadnamen im API und ist so immer ein Nomen im Plural. Die eigentlichen Daten (also z. B. “name”: “FC Essen”) der Entität werden im Element attributes geführt. Es scheint untypisch, dass Daten von der Objektreferenz getrennt gehalten werden. Im klassischen RDBMS oder in JPA werden Daten und Identifier meist gleichberechtigt in der Entity geführt. Die Trennung ist dennoch sinnvoll, da type und id rein technische Auszeichnungen des Objekts sind und nicht mit den fachlichen Attributen vermischt werden sollten. Durch Weglassen aller anderen Attribute erhalten wir außerdem jederzeit die Objektreferenz {“id”: “1”, “type”: “teams”}.

 

Nach Datenobjekten suchen: Wo ist mein Team?

Für die Suche nach unseren Teams hängen wir wie gewohnt URL-Abfrageparameter an den URL-Pfad. Um nach Attributwerten zu filtern, sieht die Spezifikation Parameter mit dem Namensmuster filter[{attribute_name}] vor. Die Unterscheidung in den Attributen erfolgt über den assoziativen Parameter {attribute_name}. Die Suche nach dem Team „FC-Norden“ sieht dann beispielsweise folgendermaßen aus:

 

GET /teams?filter[name]=FC+Norden

Filterparameter lassen sich mit einem logischen UND verknüpfen:

GET /teams?filter[name]=FC+Norden+Jugend&amp;filter[category]=juniors

oder können ein Set von Werten enthalten, was einem logischen ODER entspricht:

 

GET /teams?filter[category]=juniors,masters

 

Der reservierte Name filter hat den großen Vorteil, dass für den Anwender des API sofort klar wird, wozu der URL-Parameter verwendet wird.

 

Im Ergebnis blättern: vor und zurück …

Das Prinzip von assoziativen Parametern ist auch beim Blättern in der Ergebnisliste eine elegante Lösung: Zwei URL-Parameter, page[number] und page[size], genügen. Für den Anwender ist auch ohne Dokumentation die Bedeutung des Parameters sofort ersichtlich. Folgende Abfrage liefert Teams auf Seite drei mit maximal zehn Ergebnissen pro Seite (Listing 2).

Listing 2: Blättern in der Ergebnisliste

GET /teams?page[number]=3&amp;page[size]=10 HTTP/1.1
Accept: application/vnd.api+json
  
{
   "data": [
   { /*...*/ },
   { /*...*/ },
   /*...*/
   ],
   "links": {
      "first": "http://example.com/teams?[number]=1&amp;page[size]=10",
      "prev": "http://example.com/teams?page[number]=2&amp;page[size]=10",
      "next": "http://example.com/teams?page[number]=4&amp;page[size]=10"
      "last": "http://example.com/teams?page[number]=200&amp;page[size]=10"
   }
}

Im Antwortdokument werden die Links für das Blättern geliefert. Die Angabe dazu findet sich im links-Element parallel zu data. Somit wird auch klar, warum es vorteilhaft ist, die eigentlichen Daten in einem eigenen data-Element zu liefern. Auf der obersten Ebene lassen sich zusätzlich weitere wichtige Daten an den Empfänger des Ergebnisses übermitteln. Diese Eigenschaft des JSON-API-Dokuments ist auch bei der Behandlung von Fehlern nützlich.

 

Ein neues Datenobjekt anlegen: Willkommen FC Oldenburg

Wie legen wir nun ein neues Team an? Auch hier kommt der API-Pfad /teams zum Einsatz. Allerdings verwenden wir die HTTP-Methode POST. Der Request sieht nun so aus wie in Listing 3 dargestellt.

Listing 3

  POST /teams HTTP/1.1  
  Content-Type: application/vnd.api+json  
  Accept: application/vnd.api+json  
  {  
     "data": {  
        "type": "teams",  
        "attributes": {  
           "name": "FC Oldenburg",  
           "category": "seniors"
        }
     }
}

Wieder benutzen wir das Dokumentattribut data zur Übertragung der eigentlichen Teamattribute. Die id wird in diesem Beispiel vom Server vergeben und ist im Dokument daher nicht angegeben. Es stellt sich die Frage, warum die Angabe “type”: “teams” nicht ebenfalls entfallen kann: Lässt sich der type nicht ohnehin aus dem API-Pfad /teams ableiten? Das ist nur bedingt der Fall. Handelt es sich bei der Angabe im Pfad nämlich um einen abstrakten Basistyp, der verschiedene polymorphe Unterklassen kennt, dann benötigen wir für die Erzeugung einen konkreten (nicht abstrakten) Datentyp. Um also robuster gegen spätere Erweiterungen im Datenmodell zu sein, definieren wir das Feld type daher von vorn herein als zwingend. Als Antwort auf den POST Request wird das angelegte Team als Dokument geliefert (Listing 4).

Listing 4

{
   "data": {
      "type": "teams",
      "id": "3",
      "attributes": {
         "name": "FC Oldenburg",
         "category": "seniors"
      }    
      "links": {
         "self": "http://example.com/teams/3"
      }
   }
}

Die id wurde vom Server vergeben und ist nun ebenfalls im Dokument enthalten. Ein Link zur Ressource selbst wird im Feld self zurückgegeben.

HTTP-Methode POST

Wichtig zu wissen ist, dass ein POST per Definition den Status in einer Domäne verändert und somit nicht idempotent ist. Jeder POST führt zu einem neuen Ergebnis. Der Request POST /teams HTTP/1.1 erzeugt also jedes Mal eine neue Mannschaft. Soll ein Datenobjekt geändert werden, darf niemals POST, sondern es muss stattdessen PATCH oder PUT verwendet werden.

 

Ein Datenobjekt lesen: Zeig mir das Team mit der Nummer 3

Um ein einzelnes Datenobjekt zu lesen, wird ein API-Pfad verwendet, der dem Muster /{entity-type}/{entity-id} entspricht. Der Pfad wird also noch um die id der Entität erweitert. Es handelt sich dabei um den eindeutigen URI der zugehörigen Ressource. Der self-Link im vorangegangenen Beispiel entspricht genau solch einem URI. Das Team mit der Nummer 3 wird also mit einem einfachen HTTP GET gelesen:

 

GET /teams/3 HTTP/1.1
Accept: application/vnd.api+json

 

Als Antwort wird das Dokument mit der Referenz {“type”:”teams”, “id”:”3″} geliefert.

 

 

Ein Datenobjekt ändern: FC Essen ist gealtert …

Um ein Team zu ändern, benutzen wir wieder den URI der Ressource nach dem Muster /{entity-type}/{entity-id}. Mit der Methode PATCH können einzelne Attribute geändert werden, ohne das gesamte Objekt zu überschreiben (Listing 5).

Listing 5

PATCH /teams/2 HTTP/1.1
Accept: application/vnd.api+json
{
 "data": {
   "id": "2"
   "type": "teams",
   "attributes": {
    "category": "seniors"
   }
 }
}

Wir ändern lediglich das Attribut category, das Attribut name hingegen bleibt unverändert. Als Response wird das gesamte (geänderte) Dokument zurückgeliefert.
Eine HTTP-PATCH-Anfrage ist idempotent. Jeder Aufruf mit den gleichen Daten führt zum gleichen Status in der Domäne. Ein wiederholter, gleichlautender Aufruf ändert keine Daten am Server bzw. in unserer Datenbank.

 

Ein Datenobjekt löschen: FC Oldenburg gibt’s nicht mehr

Um ein Team zu löschen, verwenden wir, genau wie beim Lesen und Ändern, den URI der Ressource nach dem Muster /{entity-type}/{entity-id}. Als HTTP-Methode setzen wir jetzt einfach DELETE ein:

 

DELETE /teams/3 HTTP/1.1

 

Als Antwort erhalten wir einen entsprechenden HTTP-Statuscode, also 200 OK, wenn das Löschen erfolgreich war, oder 404 Not Found, wenn das zu löschende Objekt nicht existiert.

 

 

Über Beziehungen: Wer gehört zu wem?

Aus dem Datenmodell für unser Beispiel geht hervor, dass einem Team ein Manager und mehrere Spieler zugeordnet werden können. Sowohl Manager als auch Spieler sind vom Typ persons. Um den Manager und die Spieler eines Teams über das API verwalten zu können, wird der Begriff Relationship eingeführt. Das folgende Beispiel zeigt, wie die Beziehung manager im Element relationships abgebildet wird (Listing 6).

Listing 6: Beziehung „manager“-„relationships“

GET /teams/3 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
  "id:": "3"
  "type": "teams",
  "attributes": {
   "name": "FC Oldenburg"
},
  "relationships": {
   "manager": {
            "data": { "type": "persons", "id": "55" },
            "links": {
               "self": "http://example.com/teams/3/relationships/manager",
               "related": "http://example.com/teams/3/manager"
            }
         }
      }
   }
}

 

Beziehungen werden im Datenobjekt im Element relationships geliefert. Jede Relationship wird aus Sicht der verweisenden Entität über einen eindeutigen Namen identifiziert und enthält die Elemente data und links. Das data-Element kann ein oder mehrere Datenobjekte enthalten, je nach Kardinalität der Beziehung. Im konkreten Beispiel handelt es sich beim Manager um eine 1:1-Beziehung, es wird daher genau eine Referenz auf das Personen-Datenobjekt des Managers geliefert. Das links-Element enthält zwei Referenzen: Über den self-Link kann die Beziehung selbst verwaltet werden. Das Muster für den API-Pfad lautet hier: /{entity-type}/{entity-id}/relationship/{relationship-name}.
Wichtig zu erkennen ist hier, dass über den relationship-Link lediglich die Beziehung angesprochen wird. Der Link http://example.com/teams/3/relationships/manager liefert also die Relationship mit dem Namen manager, nicht das Dokument der referenzierten Person. Dieses wiederum bekommen wir mittels des zweiten Links mit dem Namen related. Der Link http://example.com/teams/3/manager liefert das Dokument mit den Daten zur Person. Auch hier sieht man, dass sich der type im Dokument nicht immer aus dem Request-URL ableiten lässt.

 

Die 1:1-Beziehung: Unser Team braucht einen Trainer!

Für das Team mit der Nummer 3 soll nun der Manager mit der Nummer 55 gesetzt werden. Für das Schreiben einer Beziehung kommt die HTTP-Methode PATCH zum Einsatz:

PATCH /teams/3/relationships/manager HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
   "data": { "type": "persons", "id": "55" }
}

Eine Beziehung, sofern sie optional ist, kann auch gelöscht werden. Das data-Element wird dazu auf null gesetzt:

PATCH /teams/3/relationships/manager HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
   "data": null
}

Der Weg zur digitalen Transformation

Developer Experience-Track entdecken

 

Die 1:n-Beziehung: Wer spielt mit?

Ähnlich verhält es sich mit der Verwaltung der Spielerbeziehungen. Im Unterschied zum Teammanager handelt es sich hier allerdings um eine 1:n-Beziehung zwischen Mannschaft und Spielern. Wir haben die Wahl zwischen HTTP-PATCH und –POST (Listing 7).

 

Listing 7

PATCH /teams/3/relationships/players HTTP/1.1
{
   "data": [
      { "type": "persons", "id": "10" },
      { "type": "persons", "id": "11" },
      { "type": "persons", "id": "12" },
      { "type": "persons", "id": "13" }
   ]
}
  
POST /teams/3/relationships/players HTTP/1.1
{
   "data": [
      { "type": "persons", "id": "17" },
      { "type": "persons", "id": "18" }
   ]
}

 

Der Unterschied ist auf den ersten Blick nicht gleich ersichtlich, wird aber schnell klarer: PATCH ersetzt die komplette Beziehung, also alle Spieler unserer Mannschaft. POST hingegen fügt die angegebenen Personen zu den bereits bestehenden Spielerbeziehungen hinzu, sofern sie noch nicht existieren. Analog können eine oder mehrere Spielerbeziehungen über ein HTTP-DELETE wieder gelöscht werden.

DELETE /teams/3/relationships/players HTTP/1.1
{
"data": { "type": "persons", "id": "10" }
}

Auch beim DELETE kann das data-Element wieder eine einzelne Referenz oder ein Array, eine Liste von Referenzen, enthalten.

Zugehörige Objekte einbinden: Ich will alles sehen!

Mit den bisher gezeigten Möglichkeiten wäre es sehr umständlich, alle Daten für ein bestimmtes Team, also die gesamte Mannschaft inklusive Trainer- und Spielernamen, zu laden. Zunächst müsste man das Teamobjekt lesen. Daraufhin müsste der Entwickler für jede Relation einen weiteren API-Aufruf absetzen, um die zugehörigen Personenobjekte zu laden. Das wäre weder performant, noch wäre der Spaß beim Programmieren sehr groß. JSON API spezifiziert hier eine elegante Möglichkeit, mit einem einzigen API-Aufruf alle benötigten Beziehungsobjekte gleich mitzuladen. Der URL-Parameter include ist dafür reserviert. Soll zum Team auch die Person zur Beziehung manager geladen werden, setzen wir als URL-Parameter ?include=manager (Listing 8).

 

 

Listing 8: Beziehungsobjekte laden

GET /teams/3?include=manager HTTP/1.1
Accept: application/vnd.api+json
  
{
   "data": {
      "id:": "3"
      "type": "teams",
      "attributes": {
         "name": "FC Oldenburg"
      },
      "relationships": {
         "manager": {
            "data": { "type": "persons", "id": "55" },
            "links": {
               "self": "http://example.com/teams/3/relationships/manager",
               "related": "http://example.com/teams/3/manager"
            }
         }
      }
   },
   "included": [
      {
         "id:": "55"
         "type": "persons",
         "attributes": {
            "name": "Coach Maier"
         }
      }
   ]
}

 

Die Daten des Trainers werden nun im Element included parallel zum Element data geliefert. Analog lassen sich auch die Spieler mit ein- und demselben Aufruf anfordern (Listing 9).

 

Listing 9: Spieler anfordern

GET /teams/3?include=manager,players HTTP/1.1
Accept: application/vnd.api+json
  
{
   "data": {
      "id:": "3", "type": "teams",
      "attributes": { "name": "FC Oldenburg" },
      "relationships": {
         "manager": {
            "data": { "type": "persons", "id": "55" },
            "links": { "related": "http://example.com/teams/3/manager" }
         },
         "players": {
            "data": [
               { "type": "persons", "id": "10" },
               { "type": "persons", "id": "11" }
            ],
            "links": { "related": "http://example.com/teams/3/players" }
         }
      }
   },
   "included": [
      {
         "id:": "55", "type": "persons",
         "attributes": { "name": "Coach Maier" }
      },
      {
         "id:": "10", "type": "persons",
         "attributes": { "name": "Johnny Wirbelwind" }
      },
      {
         "id:": "11", "type": "persons",
         "attributes": { "name": "Franz Luftikus" }
      }
   ]
}

 

Der include-Parameter eignet sich idealerweise auch bei der Suche, also der Abfrage einer Liste von Datenobjekten:

GET /teams?include=manager

 

Die include-Methode und die zugehörige Ergebnisstruktur haben ein paar wesentliche Vorteile gegenüber herkömmlichen Ansätzen. Die Daten der einzelnen Objekte bleiben streng getrennt. Ein inkludiertes Objekt ist nur einmal im Dokument vorhanden, auch wenn es mehrfach referenziert wird. Schließlich wird auch vermieden, dass das API speziell auf die Anforderungen verschiedener API-Konsumenten zugeschnitten werden muss (z. B. /teams/readAllWithManager?id=3 oder ähnliche Auswüchse).

Hier gilt es zu beachten, dass serverseitig ein solcher Request zu sehr aufwändigen Datenbankabfragen führen kann. In unserem Beispiel würde etwa die Anfrage /teams?include=manager,players die gesamte Mannschaftenliste inklusive aller Trainer- und Spielerdaten ausliefern. Denkt man kurz über die entsprechende SQL-Datenbankabfrage mittels Joins nach, kann man erahnen, dass das bei großen Datenmengen nicht unbedingt ideal ist. Es kann also sinnvoll sein, das Inkludieren bestimmter Beziehungen nicht automatisch für alle API-URL-Pfade zu erlauben. Wollen wir lediglich die Spieler eines Teams in einer Anfrage lesen, kann wieder der self-Link aus dem Relationship-Element players seinen Dienst tun. Der Link aus dem Beispiel http://example.com/teams/3/players liefert als Antwort alle Objekte vom Typ persons aus der Beziehung players.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Aktionen ausführen: Mach etwas!

Bisher wurden die klassischen Aktionen wie Create, Read, Update und Delete vorgestellt. Genügen diese Basisoperationen nicht, muss das API entsprechend erweitert werden. Eine Aktion (wie ‘suspend player from team’) führt meist auch ein Verb im Namen der Operation. Das REST-Design-Pattern empfiehlt hingegen, keine Verben im URL zu verwenden. Wie lassen sich weitere Serveroperationen in das API mit aufnehmen? Die JSON-API-Spezifikation geht auf Aktionen im API nicht näher ein. An einem Beispiel stellen wir ein paar Ansätze vor: Ein Spieler verletzt sich, und der Teammanager soll per Nachricht darüber informiert werden. Die entsprechende Methode steht bereits serverseitig zur Verfügung und soll nun in das API aufgenommen werden.

Action Verb: Ruf mich auf!

In der ersten Variante wird die Aktion in den Pfad aufgenommen: /players/1/notifyInjury. Die Verwendung des Verbs notify deutet auf eine Art Remote Procedure Call hin – etwas, das wir beim RESTful-Ansatz eigentlich vermeiden wollen. Jedenfalls handelt es sich bei notifyInjury nicht um eine Ressource im herkömmlichen Sinn. Es ist völlig unklar, welche der HTTP-Methoden GET, POST oder PATCH auf diese Pseudoressource angewandt werden sollen. Diese Methode ist daher nicht zu empfehlen.

Action Patch: Reagiere auf meine Änderung!

Soll das Verb nicht im API-Pfad vorkommen, lässt sich die serverseitige Methode auch durch einen einfachen PATCH auslösen (Listing 10).

Listing 10

PATCH /players/1 HTTP/1.1
{
   "data": {
      "id": "1"
      "type": "players",
      "attributes": {
         "condition": "injured"
      }
   }
}

 

Am Server werden Updates auf das Attribut condition entsprechend überwacht, und bei einer Änderung des Status wird der Teammanager über die Verletzung seines Spielers informiert. Die serverseitige Action muss bei dieser Variante idempotent implementiert werden: Der Manager darf nur bei einer Änderung des Status benachrichtigt werden, nicht bei jedem PATCH auf diese Ressource.

Action Metadaten: Schick mir einen Hinweis!

Eine weitere Möglichkeit bietet das (im JSON API spezifizierte) meta-Element (Listing 11).

 

Listing 11

POST /players/1 HTTP/1.1
{
   "data": {
      "id": "1", "type": "players"
   },
   "meta": {
   "action": "notifyInjury"
   }
}

 

Ein POST auf eine Ressource wird hier als Senden einer Aktion interpretiert. Die auszuführende Aktion wird dabei im Block meta als action mitgegeben. Zu beachten ist, dass ein POST nicht idempotent ist. Sollte der Spieler bereits verletzt sein, muss der Server mit einem Fehler reagieren. Der Vorteil dieser Variante: Kein zusätzlicher URL-Pfad wird definiert, sondern lediglich dem POST auf eine vorhandene Ressource eine Bedeutung zugeordnet.

Action Queue: Schick mir deine Aktion!

Will man die Aktion dennoch im API-Pfad führen, besteht die Möglichkeit, eine Action Queue [4] zu definieren. Der URL /players/1/actions bietet die Möglichkeit neue Actions (also Ressourcen vom Typ actions) wie gewohnt mittels POST über das API anzulegen. Optional könnten hier sogar vergangene Actions mittels GET ausgelesen werden. Der Vorteil dieser Variante: Eine Aktion ist eine (Pseudo-)Ressource und entspricht dem REST-Pattern. Der Nachteil: Es wird ein zusätzlicher Pfad im URL benötigt.

Fehlerbehandlung: Was läuft hier schief?

Statuscodes dienen dazu, den Schnittstellenbenutzer über den Ausgang der Request-Verarbeitung zu informieren. Das HTTP-Protokoll gibt Auskunft über mögliche Ergebnisse (Tabelle 1).

 

Statusgruppe Bedeutung2xx Verarbeitung ok3xx Umleitung4xx Fehlerhafte API-Bedienung5xx Serverseitiger FehlerTabelle 1: HTTP-Statuscodes

 

Die JSON-API-Spezifikation bietet die Möglichkeit, zusätzlich zum HTTP-Statuscode auch Fehlermeldungen im Element errors zu liefern (Listing 12).

Listing 12

HTTP/1.1 400 Bad Request
Content-Type: application/vnd.api+json
{
   "errors": [
      {
         "code": "100",
         "status": "400",
         "source": {"pointer": "data.attributes.name"},
         "title":   "Mandatory field",
         "detail": "Attribute ‘name‘ must not be empty"
      }
   ]
}

Hier wird auf eine fehlerhafte Bedienung des API durch den Client hingewiesen. Die Antwort enthält kein data-Element und liefert stattdessen neben dem HTTP-Status 400 (Bad Request) eine oder mehrere Fehlermeldungen mit weiteren Details. Die JSON-API-Spezifikation nennt für ein Error-Objekt noch weitere Attribute [5]. Den Einsatz von HTTP-Statuscodes sollte man nicht übertreiben. Auf HTTP-Ebene genügen in der Regel ein paar wenige Codes. Eine detaillierte Beschreibung liefert ohnehin das errors-Element des Dokuments.

 

Datentypen: Was bist du?

Auf die Verwendung von Datentypen geht die JSON-API-Spezifikation kaum ein. An dieser Stelle sei lediglich erwähnt, dass die Definition von unterstützten Typen für Attributwerte unbedingt Teil eines Richtlinienkatalogs sein sollte. Einheitliche Formate und die entsprechende JSON-Darstellung für Datum, Zeitstempel oder auch eigene Klassen wie Money und Ähnliches sollten unbedingt im Vorfeld festgelegt werden.

API-Versionierung: Welchen Dialekt sprichst du?

Der erste Schritt ist schnell gemacht. Ein API wurde entworfen und ist im Einsatz. Ab diesem Zeitpunkt sollte die Schnittstelle möglichst stabil gegenüber Änderungen sein. Zusätzliche Attribute führen in der Regel zu keinen Verwerfungen mit den Konsumenten der Schnittstelle. Clients sollten gegenüber solchen Anpassungen robust implementiert werden. Was aber, wenn sich am Schema des API Grundlegendes ändert? Sind andere Entwicklerteams oder auch externe Partner von der Änderung betroffen, kann die Synchronisierung aller notwendigen Tätigkeiten aufwendig und schwierig werden. Daher muss sich der API-Entwickler auch Gedanken über unterschiedliche Versionen eines API machen. Die JSON-Spezifikation bietet hierzu keine vordefinierte Lösung. Sofern man rückwärtskompatible, alte Aufrufe weiterhin unterstützen möchte (oder muss) bietet sich die URI-Versionierung als Lösungsansatz an:


/v1/teams
/1.0/teams
/v1.1/teams

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Wer den RESTful-Ansatz ernst nimmt, dem muss hier allerdings bewusst sein, dass mit jeder Einführung einer neuen Version aus der Sicht von außen ein vollständiges, neues Datenmodell entsteht. Niemals sollte die Verlinkung der Ressourcen untereinander auf verschiedenen API-Versionen basieren. Für einen Client sieht es so aus, als ob es sich um unabhängig voneinander existierende Datenquellen mit unterschiedlichen Datensätzen handeln würde.
Zu den Vorteilen in diesem Zusammenhang zählen die Einfachheit in der Umsetzung (abgesehen von der Rückwärtskompatibilität) und die unkomplizierte Anwendung. Ein Nachteil ist, dass dieselbe Ressource unter mehreren Pfaden abrufbar ist. Der URL für ein und dieselbe REST-Ressource ist nicht für alle Zeiten fix, und Links in externen Systemen müssen eventuell angepasst werden.
Außerdem fehlt ein kanonischer Identifikator: Zwei unterschiedliche URIs können auf dieselbe dahinterliegende Resource verweisen, ohne dass dies dem Client bewusst ist. Für weitere Ansätze zum Thema Versionierung verweisen wir auf das Buch „Build APIs You Won’t Hate“ [6].

Fazit: Wir sind gewappnet

Ein starker Trend in der Softwareindustrie geht weg vom Monolith hin zu kleinen Domain- und Microservices. Diese Entwicklung erfordert stabile und gut durchdachte APIs, um nicht im Schnittstellenchaos zu versinken. Die JSON-API-Spezifikation bietet sehr viele und gute Lösungsansätze rund um das Thema RESTful JSON. Eigene Richtlinien lassen sich aus ihr leicht ableiten. Von Anfang an gut gegen den API-Wildwuchs gewappnet, steht einer erfolgreichen Lösung nichts mehr im Weg.

 

Links & Literatur
[1] JSON-API-Spezifikation: http://jsonapi.org
[2] Keywords for use in RFCs to Indicate Requirement Levels: https://www.ietf.org/rfc/rfc2119.txt
[3] JSON API/Document Structure: http://jsonapi.org/format/#document-structure
[4] Thoughts on RESTful API Design: http://restful-api-design.readthedocs.io/en/latest/methods.html#actions
[5] JSON API/Errors: http://jsonapi.org/format/#errors
[6] Sturgeon, Philip: „Build APIs You Won’t Hate“: https://leanpub.com/build-apis-you-wont-hate

The post RESTful APIs richtig gemacht – Anleitung für bessere REST-Schnittstellen appeared first on JAX.

]]>
Die Bedeutung von Codereviews – Fünf Thesen zu Scrum https://jax.de/blog/software-architecture-design/oliver-kraeft-fuenf-thesen-zu-scrum/ Thu, 25 Jan 2018 14:42:38 +0000 https://jax.de/?p=61749 Oliver Kraeft erläutert in seinem Interview anhand der "Fünf Thesen zu Scrum" von Bernhard Löwenstein die Wichtigkeit von Codereviews, um eine hohe Codequalität zu sichern.

The post Die Bedeutung von Codereviews – Fünf Thesen zu Scrum appeared first on JAX.

]]>

 

THESE 1

Bernhard Löwenstein: Die meisten Unternehmen machen nicht Scrum, sondern verwenden lediglich einen Scrum-ähnlichen Prozess. So werden beispielsweise immer wieder die Daily Stand-ups oder die Retrospektiven nicht gemacht. Dadurch geht aber manch großer Vorteil verloren, dessen sich die Unternehmen aber gar nicht bewusst sind.

Oliver Kraeft: Da stimme ich zu. Oft habe ich die Variante erlebt, dass nur das Daily Scrum stattfindet oder einzelne Teile des Scrum-Prozesses nicht vorkommen. Bei der Scrum-Master-Zertifizierung lernt man, dass man zwar etwas aus dem Prozess weglassen kann, es dann aber kein Scrum mehr ist. Aber auch die Variante, dass zwar Scrum verwendet wird, es aber nicht so genannt wird, habe ich kennengelernt.

Den Scrum-Prozess zu verwenden, ist eine große Umstellung für das Team. Die eigenen Tätigkeiten sind für alle sichtbar und sie ändern sich mitunter gegenüber dem klassischen Vorgehen. Das ist nicht jedermanns Sache.

Durch die Retrospektive verbessert sich der Prozess mit jedem Sprint.                                                                                                                                                                                    .

 

THESE 2

Bernhard LöwensteinBei den früher klassischen Vorgehensmodellen gab es immer einen großen Aufschrei der Entwickler, wenn Codeteile im Nachhinein geändert werden mussten. Bei Scrum sind in den meisten Projekten die zwischendurch durchgeführten Codeadaptierungen noch wesentlich umfangreicher, allerdings wird dies nicht bewusst wahrgenommen. Um es mit einer Metapher auszudrücken: Am Ende steht zwar ein fertiges Haus, aber auf dem Weg dahin hat man oft in Summe auch ein ganzes Haus wieder eingerissen.

Oliver Kraeft: Ich denke, die Softwareentwicklung hat sich im Laufe der Jahre stark verändert, beispielsweise durch Bücher wie „Design Patterns“, „Clean Code“ oder „Refactoring“ und auch durch die testgetriebene Softwareentwicklung. Dadurch hat sich der Fokus in Richtung bessere Codequalität bzw. laufende Codeverbesserung bewegt. Heute ist Refactoring Stand der Dinge, zumindest wenn es eine gute Testabdeckung gibt. Die gängigen IDEs unterstützen einen dabei. Durch das iterative Vorgehen kommt es zwangsläufig dazu, dass schon vorhandener Code verändert werden muss und deswegen Wert auf die Wart- und Veränderbarkeit gelegt wird.

 

 

Der Agile & Culture Track auf der W-JAX 2018

 

THESE 3

Bernhard LöwensteinEs wird bei Scrum vorab kaum mehr etwas spezifiziert, sondern die Produktivität wird vorrangig daran gemessen, wie schnell die Entwickler loslegen und etwas Herzeigbares liefern. Das endet oftmals in Code, der deutlich eleganter geschrieben hätte werden können, oder in Architekturen, die sich später als suboptimal herausstellen.

Oliver Kraeft: Das kann zwar passieren, hat meiner Meinung aber nichts mit Scrum zu tun. Wenn es in einem Scrum-Prozess passiert, würde es auch ohne Scrum passieren. Man kann durchaus mehrere Sprints für Architekturüberlegungen und Prototypen verwenden. Um zu erreichen, dass die Codequalität hoch genug ist, benötigt man Codereviews, das sollte zur Definition of Done gehören. Es sei denn, alle Entwickler sind so brillant, dass das nicht nötig ist. Das ist aber meist nicht der Fall. Ich halte Codereviews jedenfalls für sehr wichtig.

Bei der Scrum-Master-Zertifizierung lernt man, dass man zwar etwas aus dem Prozess weglassen kann, es dann aber kein Scrum mehr ist.

 

THESE 4

Bernhard LöwensteinEntwickler lieben Scrum vor allem deshalb, da die lästige Arbeit des Dokumentierens auf ein Minimum reduziert wird und das Programmieren im Vordergrund steht. Das erzeugt den Eindruck, dass man stets produktiv ist, auch wenn dies gesamtheitlich betrachtet nicht so der Fall ist.

Oliver Kraeft: Dieser These stimme ich nicht zu. Ob das Dokumentieren Teil der Arbeiten eines Softwareentwicklers ist, hat nichts mit Scrum zu tun. Die Dokumentation muss gewünscht sein, und dann ist sie ein Teil des Sprint Backlogs. Dokumentation muss ebenfalls Bestandteil der Definition of Done sein. In meinem aktuellen Projekt erfolgt ein Teil der Dokumentation durch die Tests, wodurch diese automatisch immer aktuell ist.

 

THESE 5

Bernhard LöwensteinWer Scrum mit den früher klassischen Vorgehensmodellen vergleicht, macht oft den Fehler, dass er Scrum inklusive der heute bereitstehenden Werkzeuge und Techniken mit dem früher klassischen Prozess exklusive dieser Werkzeuge und Techniken vergleicht (da es diese eben damals noch nicht gab). Klarerweise scheint Scrum dann den „alten“ Vorgehensmodellen deutlich überlegen zu sein.

Oliver Kraeft: Aus meiner Sicht ist Scrum den klassischen Vorgehensmodellen aus drei Gründen überlegen:

  • Scrum beachtet eine der wichtigen Eigenschaften der Softwareentwicklung, nämlich dass sie ein iterativer (sich in kleinen Schritten wiederholender) Prozess ist.
  • In regelmäßigen Abständen erhält der Kunde eine lauffähige Software, sodass Fehlentwicklungen schnell erkannt werden.
  • Durch die Retrospektive verbessert sich der Prozess mit jedem Sprint.

 

 

 

Free: Mehr als 40 Seiten Java-Wissen


Lesen Sie 12 Artikel zu Java Enterprise, Software-Architektur und Docker und lernen Sie von W-JAX-Speakern wie Uwe Friedrichsen, Manfred Steyer und Roland Huß.

Dossier herunterladen!

The post Die Bedeutung von Codereviews – Fünf Thesen zu Scrum appeared first on JAX.

]]> Warum möchten Sie agil werden? – Fünf Thesen zu Scrum https://jax.de/blog/software-architecture-design/warum-moechten-sie-agil-werden-fuenf-thesen-zu-scrum/ Mon, 18 Dec 2017 15:20:48 +0000 https://jax.de/?p=61703 Entweder Scrum oder gar nichts! In diesem Interview erklärt Thomas Much, warum die Verwendung eines Scrum-ähnlichen Prozesses kontraproduktiv ist.

The post Warum möchten Sie agil werden? – Fünf Thesen zu Scrum appeared first on JAX.

]]>
 

 

THESE 1

Bernhard Löwenstein: Die meisten Unternehmen machen nicht Scrum, sondern verwenden lediglich einen Scrum-ähnlichen Prozess. So werden beispielsweise immer wieder die Daily Stand-ups oder die Retrospektiven nicht gemacht. Dadurch geht aber manch großer Vorteil verloren, dessen sich die Unternehmen aber gar nicht bewusst sind.

Thomas Much: Es geht nicht nur mancher Vorteil verloren, sondern es entsteht auch häufig Frust, weil die Beteiligten merken, dass das Vorgehen nicht stimmig ist. Häufig fühlt sich Scrum dann „kaputt“ an und Agilität wird künftig abgelehnt. Es ist ein nicht zu unterschätzender Aufwand, vernünftige Daily Stand-ups und hilfreiche Retrospektiven durchzuführen. Denn offene Kommunikation, stolz sein auf Stärken und das Lernen aus Fehlern müssen viele Teams – und deren Leiter – oft erst noch lernen! Ein Unternehmen sollte sich daher immer zuerst die Frage stellen, warum es agil werden bzw. Scrum machen will. Möchte man die Teams befähigen, autonomer zu handeln, um dadurch mehr Flexibilität zu erreichen? Dann kann man die Mühe des Lernens auf sich nehmen, was letztendlich das ganze Unternehmen verändern wird. Oder möchte man sich nur möglichst schnell „agil“ auf die Fahnen schreiben können und verpackt daher den bisherigen (starren oder nicht vorhandenen) Prozess in einem Buzzword? Das ist in aller Regel kontraproduktiv.

 

 

THESE 2

Bernhard LöwensteinBei den früher klassischen Vorgehensmodellen gab es immer einen großen Aufschrei der Entwickler, wenn Codeteile im Nachhinein geändert werden mussten. Bei Scrum sind in den meisten Projekten die zwischendurch durchgeführten Codeadaptierungen noch wesentlich umfangreicher, allerdings wird dies nicht bewusst wahrgenommen. Um es mit einer Metapher auszudrücken: Am Ende steht zwar ein fertiges Haus, aber auf dem Weg dahin hat man oft in Summe auch ein ganzes Haus wieder eingerissen.

Thomas MuchIn der Tat, weil Refactoring fester Bestandteil agiler Softwareentwicklung ist. Es ist durchaus üblich, 20 Prozent der Entwicklungszeit für Refactorings zu verwenden. Dabei kann es passieren, dass letztendlich der gesamte Code umgeschrieben wird, manche Codestellen sogar mehrfach. Wenn neue Anforderungen entstehen oder das Team neue Erkenntnisse gewinnt, sollte der betroffene Code zeitnah umgebaut werden. Wenn man das auf irgendwann später verschiebt, passiert das nie und der Code verrottet. Oder man hat mühsam ein komplettes Hochhaus gebaut, merkt nun aber, dass man nur ein Reihenhaus benötigt. Einen solchen Umbau nach Projektende durchzuführen, ist unglaublich aufwendig und stößt zurecht auf Widerstand. Dann doch lieber in kleinen Schritten während der Entwicklung.

Die Refactorings werden sehr wohl bewusst wahrgenommen, weil nicht nur der Code geändert, sondern auch die betroffenen Tests angepasst werden müssen. Ein agiles Team begreift die regelmäßigen Änderungen aber als Chance, die Codebasis wartbar und möglichst klein zu halten.

Die besten Werkzeuge für Scrum sind ein möglichst großes Whiteboard, Post-its und Stifte.

 

Der Agile & Culture Track auf der JAX 2019

THESE 3

Bernhard LöwensteinEs wird bei Scrum vorab kaum mehr etwas spezifiziert, sondern die Produktivität wird vorrangig daran gemessen, wie schnell die Entwickler loslegen und etwas Herzeigbares liefern. Das endet oftmals in Code, der deutlich eleganter geschrieben hätte werden können, oder in Architekturen, die sich später als suboptimal herausstellen.

Thomas Much: Das beschreibt weder Scrum noch irgendein agiles Vorgehen, sondern ungeplantes Programmieren auf Zuruf. Das mag unglaublich agil klingen, endet aber oft im Chaos. Leider ist das noch zu oft die Projektrealität. Agilität beschreibt dagegen ein sehr planvolles Vorgehen – nur, dass nicht mehr alles im Vorfeld durchplant wird. Je näher die Umsetzung einer Aufgabe rückt, desto genauer wird sie geplant. Weiter entfernte Aufgaben werden nur grob geplant. Wenn sich Anforderungen ändern oder neu ergeben, wird neu priorisiert und geplant. Und erst nach der Detailplanung einer Aufgabe wird mit deren Umsetzung begonnen.

Damit das funktioniert, brauchen wir bei agilem Vorgehen mehr denn je ein klares, sinnvolles Ziel, da der Weg dahin noch nicht exakt beschrieben ist. Ein Hilfsmittel zur Visualisierung des Ziels und der Reise dorthin kann beispielsweise User Story Mapping sein. Und egal, wie gut wir planen – agil oder nicht agil – Code und Architektur werden sich im Laufe der Zeit immer als suboptimal herausstellen. Agilität akzeptiert dies als inhärentes Problem der Softwareentwicklung und plant solche notwendigen Änderungen regelmäßig ein (siehe These 2).

Bei agilem Vorgehen brauchen wir mehr denn je ein klares, sinnvolles Ziel, da der Weg dahin noch nicht exakt beschrieben ist.

 

THESE 4

Bernhard LöwensteinEntwickler lieben Scrum vor allem deshalb, da die lästige Arbeit des Dokumentierens auf ein Minimum reduziert wird und das Programmieren im Vordergrund steht. Das erzeugt den Eindruck, dass man stets produktiv ist, auch wenn dies gesamtheitlich betrachtet nicht so der Fall ist.

Thomas Much: Wenn durch das Weglassen überflüssiger Dokumentation die Gesamtproduktivität sinkt, ist irgendetwas anderes im agilen Vorgehen nicht in Ordnung, beispielsweise die Planung (siehe These 3). Denn solange das Team das sinnvolle Minimum an Dokumentation schreibt, ist dagegen nichts einzuwenden. Scrum bzw. Agilität darf nur kein Freibrief dafür sein, dass man gar keine Dokumentation mehr erstellt.

Sinnvolle Dokumentation ist beispielsweise ein Überblick über die fachlichen und technischen Zusammenhänge oder die Pflege des Betriebshandbuchs. Das kann ein Standardtask am Ende jeder Story als Teil der Definition of Done sein. Die übrige Dokumentation inklusive der meisten Codekommentare lässt sich durch eleganten Code ersetzen: ordentlich benannte Variablen, Methoden und Klassen sowie verständliche Unit-Tests. Solche Art von Dokumentation ist für ein gut funktionierendes, crossfunktionales agiles Team essenziell. Wer darauf verzichten möchte, nimmt Scrum vermutlich nur als Vorwand, um alleine vor sich hin hacken zu können.

Ein agiles Team begreift die regelmäßigen Änderungen aber als Chance, die Codebasis wartbar und möglichst klein zu halten.

 

THESE 5

Bernhard LöwensteinWer Scrum mit den früher klassischen Vorgehensmodellen vergleicht, macht oft den Fehler, dass er Scrum inklusive der heute bereitstehenden Werkzeuge und Techniken mit dem früher klassischen Prozess exklusive dieser Werkzeuge und Techniken vergleicht (da es diese eben damals noch nicht gab). Klarerweise scheint Scrum dann den „alten“ Vorgehensmodellen deutlich überlegen zu sein.

Thomas Much: Die besten Werkzeuge für Scrum sind ein möglichst großes Whiteboard, Post-its und Stifte. Die gab es auch früher schon. Und eigentlich sind die Techniken für die klassischen Prozessmodelle viel ausgefeilter als die von Scrum, auch weil sie über einen viel längeren Zeitraum entstanden sind und eingesetzt wurden. Und dennoch haben sich die klassischen Techniken für viele Projekte als ungeeignet erwiesen. Scrum bzw. agiles Vorgehen ist dann besser geeignet, wenn die Detailanforderungen sich erst im Projektverlauf klären oder ändern, weil Scrum das Risiko von Änderungen akzeptiert und ein Vorgehen dafür beschreibt, wie damit geordnet umgegangen wird.

Projekte, deren Anforderungen im Vorfeld exakt feststehen (müssen), sind mit einem anderen klassischen Vorgehensmodell vermutlich besser beraten. Aber die vielen Projekte, bei denen das Management die Augen davor verschließt, dass die Anforderungen beim Projektstart eben nicht exakt feststehen – für die ist ein agiles Vorgehen tatsächlich „überlegen“, weil das klassische Vorgehen in diesem Fall schlicht ungeeignet ist.

 

 

Microservices-Dossier:
Mehr als 30 Seiten Wissen


Lesen Sie 7 Artikel über Microservices von Experten wie Eberhard Wolff, Adam Bien, Manfred Steyer, Michael Hofmann und Weiteren.

Dossier herunterladen!

The post Warum möchten Sie agil werden? – Fünf Thesen zu Scrum appeared first on JAX.

]]>
Eigenverantwortung als Motivator – Fünf Thesen zu Scrum https://jax.de/blog/software-architecture-design/eigenverantwortung-als-motivator-fuenf-thesen-zu-scrum/ Mon, 11 Dec 2017 15:43:16 +0000 https://jax.de/?p=61681 Michael Schaffler diskutiert Löwensteins Thesen zu Scrum und betont dabei, dass die Steigerung der Agilität der Geschäftsmodelle auch seinen Niederschlag in der Softwareentwicklung finden müsse.

The post Eigenverantwortung als Motivator – Fünf Thesen zu Scrum appeared first on JAX.

]]>
 

 

THESE 1

Bernhard Löwenstein: Die meisten Unternehmen machen nicht Scrum, sondern verwenden lediglich einen Scrum-ähnlichen Prozess. So werden beispielsweise immer wieder die Daily Stand-ups oder die Retrospektiven nicht gemacht. Dadurch geht aber manch großer Vorteil verloren, dessen sich die Unternehmen aber gar nicht bewusst sind.

Michael Schaffler: Geringfügige Anpassungen sollten erlaubt sein, um die speziellen Gegebenheiten innerhalb eines Unternehmens berücksichtigen zu können. Eine Methode zu dogmatisieren und sie stur in einer unreflektierten Weise zu befolgen, bringt größere Nachteile mit sich, als sich Freiraum für Adaptierungen zu lassen.

Das Wichtigste ist zu verstehen, was das Unternehmen an Funktionalität benötigt, und eine frühe Rückkopplungsschleife zu etablieren.

 

THESE 2

Bernhard LöwensteinBei den früher klassischen Vorgehensmodellen gab es immer einen großen Aufschrei der Entwickler, wenn Codeteile im Nachhinein geändert werden mussten. Bei Scrum sind in den meisten Projekten die zwischendurch durchgeführten Codeadaptierungen noch wesentlich umfangreicher, allerdings wird dies nicht bewusst wahrgenommen. Um es mit einer Metapher auszudrücken: Am Ende steht zwar ein fertiges Haus, aber auf dem Weg dahin hat man oft in Summe auch ein ganzes Haus wieder eingerissen.

Michael Schaffler: Die Frage ist letztendlich, ob Kent Beck Recht behält, dass ständiges Refactoring des Codes in Summe weniger aufwendig ist, als eine vollständige Spezifikation und Architektur zu entwerfen und diese in einem umzusetzen. Welcher Ansatz der ökonomischere ist – und das sollte die Maxime sein – hängt wahrscheinlich auch von den Geschäftsmodellen der Unternehmen ab. Eines ist klar, die Unternehmen müssen heute wesentlich schneller auf Veränderungen im Markt reagieren, als dies vor zwanzig Jahren der Fall war. Diese Steigerung der Agilität der Geschäftsmodelle muss auch seinen Niederschlag in der Softwareentwicklung finden.

 

 

Der Agile & Culture Track auf der JAX 2018

THESE 3

Bernhard LöwensteinEs wird bei Scrum vorab kaum mehr etwas spezifiziert, sondern die Produktivität wird vorrangig daran gemessen, wie schnell die Entwickler loslegen und etwas Herzeigbares liefern. Das endet oftmals in Code, der deutlich eleganter geschrieben hätte werden können, oder in Architekturen, die sich später als suboptimal herausstellen.

Michael Schaffler: Das Wichtigste ist zu verstehen, was das Unternehmen an Funktionalität benötigt, und eine frühe Rückkopplungsschleife zu etablieren. Wenn Analyse und Umsetzung von einem Team bewerkstelligt wird und dieses noch dazu eng mit den Fachbereichen zusammenarbeitet, so kann die Dokumentation auf ein Minimum reduziert werden. Kommt es hier zu einem Bruch (strikte Trennung von Anforderungsanalyse und Umsetzung), so wird geschriebene Dokumentation in höherem Maße notwendig, sonst geht Wissen verloren. Scrum propagiert Ersteres und kann damit zu Recht auf eine hohe Dokumentationslast verzichten.

Unternehmen müssen heute wesentlich schneller auf Veränderungen im Markt reagieren, als dies vor zwanzig Jahren der Fall war.

 

THESE 4

Bernhard LöwensteinEntwickler lieben Scrum vor allem deshalb, da die lästige Arbeit des Dokumentierens auf ein Minimum reduziert wird und das Programmieren im Vordergrund steht. Das erzeugt den Eindruck, dass man stets produktiv ist, auch wenn dies gesamtheitlich betrachtet nicht so der Fall ist.

Michael Schaffler: Dem würde ich widersprechen. Entwickler mögen Scrum, weil sie Planung und Aufgabenteilung eigenverantwortlich mitbestimmen, anstatt Aufwände und Arbeitspakete von einem Projektleiter aufs Auge gedrückt zu bekommen. Eigenverantwortung stärkt die Motivation, und Motivation ist der Schlüssel jedes Erfolgs.

 

THESE 5

Bernhard LöwensteinWer Scrum mit den früher klassischen Vorgehensmodellen vergleicht, macht oft den Fehler, dass er Scrum inklusive der heute bereitstehenden Werkzeuge und Techniken mit dem früher klassischen Prozess exklusive dieser Werkzeuge und Techniken vergleicht (da es diese eben damals noch nicht gab). Klarerweise scheint Scrum dann den „alten“ Vorgehensmodellen deutlich überlegen zu sein.

Michael Schaffler: Die Tools spielen in Scrum eine untergeordnete Rolle. Scrum hat hier gegenüber althergebrachter Methoden keine Vorteile. Die große Schwäche von Scrum ist aus meiner Sicht, dass keine Antwort auf die Frage gegeben wird, wie mittelfristige Planung anzugehen ist und wie Festpreisprojekte abgewickelt werden sollten. Hier gäbe es Potenzial zur Weiterentwicklung. Stattdessen ist aber eine Mystifizierung und sektenartige Einschwörung auf den Prozess zu beobachten.

 

Erfahren Sie mehr über SCRUM auf der JAX 2018


● Was macht ein Scrum Master den ganzen Tag?

 

Free: Mehr als 40 Seiten Java-Wissen


Lesen Sie 12 Artikel zu Java Enterprise, Software-Architektur und Docker und lernen Sie von W-JAX-Speakern wie Uwe Friedrichsen, Manfred Steyer und Roland Huß.

Dossier herunterladen!

The post Eigenverantwortung als Motivator – Fünf Thesen zu Scrum appeared first on JAX.

]]>