Meecrowave — Microservices ohne aufwändiges Framework

Björn Berg
Cofinpro
Published in
4 min readJun 30, 2020

Mit Meecrowave hat die Apache Foundation einen Microprofile-Server auf den Weg gebracht, der durch seine geringe Größe und niedrigen Speicherverbrauch besticht. Inwiefern er genutzt werden kann, um einfach Microservices zu schreiben, habe ich mir genauer angesehen.

Apache Meecrowave ist ein Server, der ausschließlich auf Technologien der Apache Foundation basiert und in seiner Basisausstattung überschaubar ist. Im Kern werkelt ein Tomcat-Server als Servlet-Container. Außerdem sind die folgenden Komponenten enthalten:

Zum Erstellen eines einfachen Microservice ist lediglich eine Maven-Abhängigkeit zu definieren, und schon kann mit der Entwicklung des eigenen Microservice begonnen werden.

<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-core</artifactId>
<version>${meecrowave.version}</version>
</dependency>

Meecrowave ist derzeit in der Version 1.2.9 verfügbar. Die Version stammt von September 2019. Seitdem hat sich an den EE-Spezifikationen wenig verändert, sodass keine neuen Komponenten veröffentlicht worden sind.

Mit dem Maven-Goal meecrowave:bundle lässt sich ein ZIP-Archiv erzeugen, das alle notwendigen Abhängigkeiten und Start-Skripte beinhaltet, sodass ein in sich geschlossener Service ausgeliefert oder in einen Docker-Container deployt werden kann.

Doch zurück zum eigentlichen Entwickeln mit Meecrowave. Um besser nachzuvollziehen welche Möglichkeiten in Meecrowave stecken, habe ich auf GitHub ein Projekt angelegt. Es besteht aus zwei Modulen und soll ein etwas komplexeres Beispiel statt dem üblichen Hello World darstellen:

  • person-model: Enthält unsere Modellklassen, die von der Verarbeitungslogik separiert sind.
  • person-server: Enthält unseren Microservice auf Basis von Meecrowave.

Für diesen Blog-Beitrag relevant ist nur das Modul person-server. Und das schauen wir uns nun genauer an.

Eine Starterklasse anlegen

Um eine Instanz von Meecrowave starten zu können, wird eine Startklasse benötigt. Der Name der Klasse ist hierbei gleichgültig — ich habe sie Starter genannt — sie muss lediglich eine statische Main-Methode enthalten, in der Meecrowave konfiguriert und gestartet wird.

public class Starter {
public static void main(String[] args) {
Meecrowave.Builder builder = new Meecrowave.Builder();
builder.setHttpPort(8080);

try(final Meecrowave meecrowave = new Meecrowave(builder)) {
meecrowave.bake().await();
}
}
}

Eine Meecrowave-Instanz lässt sich programmatisch, per Konfigurationsdatei oder durch Parameter auf der Kommandozeile konfigurieren. Im gezeigten Beispiel wird der Listener-Port auf 8080 konfiguriert. Weitere Parameter wären über den Builder möglich. Auf den Seiten zum Meecrowave-Projekt findet sich eine lange Liste der Einstellmöglichkeiten.

Durch bake() wird der Server gestartet und mit await() in den Modus versetzt, auf eingehende Requests zu warten. Mit der laufenden Instanz im Hintergrund kann nun der eigentliche REST-Service implementiert werden.

Einen REST-Service implementieren

In dem Person-Server können die Mitglieder einer Familie angezeigt, entfernt oder neue hinzugefügt werden. Unser Beispiel implementiert also einen typischen CRUD-Service. Mit den bekannten JAX-RS Annotationen haben wir in Kürze in der Klasse PersonEndpoint einen REST-Service implementiert.

@RequestScoped
@Path("/person")
public class PersonEndpoint {
@Inject
private IPersonService personService;

@POST
public Response createPerson(Person person) {
personService.add(person);
return Response.status(Status.CREATED).entity(person).build();
}

@DELETE
public Response removePerson(Person person) throws PersonException {
personService.remove(person);
return Response.ok(person).build();
}

@GET
public Response listAll() {
return Response.ok(personService.listAll()).build();
}
[...]
}

Beim Start des Containers wird die Klasse gefunden und der Endpunkt registriert. Mit curl oder Postman kann mit den Endpunkten interagiert werden. Die eigentliche Geschäftslogik für die Bereitstellung der Daten wurde wiederum in einem eigenen Service bzw. einer eigenständigen Bean gekapselt. Die Arbeitsweise des Services dürfte durch die sprechenden Methodennamen selbsterklärend sein. Für die Details zur Arbeitsweise lohnt sich ein Blick in das Repository.

curl http://localhost:8080/person
[{"id":"e0f6bf13-2deb-4831-a779-5aa34fe7e5e6","name":"Simpson","surname":"Homer"},
{"id":"3348f546-4ab3-4af0-97c4-6ce8a2d04ce1","name":"Simpson","surname":"Marge"},
{"id":"81a038aa-5dd7-4550-9cff-b2dab4b59c9a","name":"Simpson","surname":"Bart"},
{"id":"7e5ee7a1-70d5-4540-b304-36a67bb969f1","name":"Simpson","surname":"Lisa"},
{"id":"a6e08d08-f6b2-4e9d-98b8-a80ffd5f04e7","name":"Simpson","surname":"Maggie"}]

TDD mit Meecrowave

Spring wird für seine gute Testbarkeit gelobt. Insbesondere Integrationstests sind in Spring einfacher zu erstellen als in JavaEE. Wer im EE-Kontext Integrationstests schreiben will, muss sich z.B. erst in Arquillian einarbeiten.

Die Entwickler von Meecrowave haben diesen Umstand berücksichtigt und ein zusätzliches Maven-Plugin geschaffen, das dafür sorgt, dass Test Driven Development mit Meecrowave komfortabel möglich ist. Und es arbeitet perfekt mit JUnit 4 und JUnit 5 zusammen.

<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-junit</artifactId>
<scope>test</scope>
<version>${meecrowave.version}</version>
</dependency>

Mit meecrowave-junit können in oder mehrere Endpunkte getestet werden. Dazu wird zu Beginn der Tests der Container hochgefahren. Da für die Tests ggf. Informationen aus der Konfiguration des Servers notwendig sind, können diese über die Junit 5 Erweiterung @MonoMeecrowaveConfigausgelesen werden.

@MonoMeecrowaveConfig
public class PersonEndpointTest {
private WebTarget baseTarget;

@ConfigurationInject
private Meecrowave.Builder config;

@BeforeEach
public void setUp() {
String baseUrl = String.format("http://localhost:%d/person", config.getHttpPort());
Client client = ClientBuilder.newClient();
baseTarget = client.target(baseUrl);
}

@Test
public void itListsAllExisting() {
List<Person> personList = baseTarget.request(MediaType.APPLICATION_JSON)
.get(new GenericType<List<Person>>() {});

assertNotNull(personList);
assertFalse(personList.isEmpty());
}
[...]
}

Im Beispiel ist die Verwendung von @MonoMeecrowaveConfig dargestellt. Die Annotation ist vergleichbar mit den aus Junit 4 verwendeten @RunWith-Annotation und sorgt dafür, dass der Container vor der Ausführung der eigentlichen Tests hochgefahren wird.

Mit @ConfigurationInject haben wir die Möglichkeit auf eine Meecrowave Builder-Instanz zuzugreifen und somit die Parameter des laufenden Containers abzufragen. Wir benutzen dies, um den Port in der setUp-Methode für unsere URL zu erfahren. Diese bildet die Basis für unseren JAX-RS Client, mit dem die verschiedenen Endpunkte getestet werden sollen.

Weitere Möglichkeiten

Wer mehr Funktionalitäten aus dem Java EE-Kontext benötigt, dem stellt Meecrowave noch weitere Komponenten zur Verfügung:

  • meecrowave-jpa: Hierbei handelt es sich um einen zusätzliches Layer, der auf JPA aufbaut und mit einem Builder bei der Konfiguration der Persistenzschicht hilft.
  • meecrowave-jolokia: Komponenten zur Integration von Monitoring auf Basis von Jolokia.
  • meecrowave-oauth2: Experimentelles Modul zur Einbindung einer OAuth2-Implementierung auf Basis von Apache CXF.

Weiter ist es möglich, SSL-Verschlüsselung über Let’s Encrypt einzubinden.

Zusammenfassung

Mit dem Meecrowave-Projekt hat die Apache Foundation unter der Schirmherrschaft des OpenWebBeans-Projekts einen kleinen, schlanken Microprofil-Server geschaffen, der auf dem Besten aus dem Java EE-Stack aufsetzt.

Wer keine großen Frameworks für einen einfachen Microservice benötigt, der ist bei Meecrowave genau richtig, da hier kaum Einarbeitung notwendig ist.

--

--