Microservices sind produktiv, aber ihre Infrastruktur ist komplex: Service Discovery, mTLS, State Management, Pub/Sub, Retries, Circuit Breaker, verteiltes Tracing — das alles muss jede Anwendung selbst oder über Bibliotheken integrieren. Genau hier setzt Dapr (Distributed Application Runtime) an: Es verlagert die gesamte Infrastruktur-Logik in einen Sidecar-Prozess, und dein Java-Code kommuniziert nur noch mit localhost.

Dieser Artikel zeigt, wie du Dapr 1.18 mit Spring Boot und dem Java SDK 1.17.4 einsetzt.

Was ist Dapr?

Dapr ist eine Open-Source-Laufzeitumgebung (CNCF Incubating), die als Sidecar neben deiner Anwendung läuft. Statt Redis-Client, Kafka-Producer und Service-Discovery-Logik in deine App zu packen, sprichst du die Dapr-API auf http://localhost:3500 (HTTP) oder localhost:50001 (gRPC) an.

Die Architektur:

┌─────────────┐          ┌─────────────┐
│  order-svc    │           │  payment-svc  │
│  (Spring)     │           │  (Spring)     │
└──────┬──────┘          └──────┬──────┘
         │ gRPC/HTTP                │
  ┌────▼─────┐            ┌────▼─────┐
  │  Dapr      │◄── mTLS ──►│  Dapr     │
  │  Sidecar   │            │  Sidecar   │
  └────┬─────┘            └────┬─────┘
        │                         │
  ┌────▼─────┐            ┌────▼─────┐
  │  Redis     │            │  Kafka     │
  │  State     │            │  Pub/Sub   │
  └──────────┘            └──────────┘

Installation und erster Start

<em># macOS</em>
brew install dapr/tap/dapr-cli

<em># Dapr initialisieren (startet Redis, Zipkin, Placement)</em>
dapr init

<em># Verifizieren</em>
dapr --version   <em># CLI 1.18, Runtime 1.18</em>
Code-Sprache: HTML, XML (xml)

Eine Spring-Boot-App mit Dapr starten:

dapr run --app-id order-service --app-port 8080 --dapr-http-port 3500 \
  -- mvn spring-boot:run
Code-Sprache: CSS (css)

Java SDK Setup

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.dapr</groupId>
            <artifactId>dapr-sdk-bom</artifactId>
            <version>1.18.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>io.dapr</groupId>
        <artifactId>dapr-sdk</artifactId>
        <version>1.17.4</version>
    </dependency>
    <dependency>
        <groupId>io.dapr</groupId>
        <artifactId>dapr-sdk-springboot</artifactId>
        <version>1.17.4</version>
    </dependency>
</dependencies>
Code-Sprache: HTML, XML (xml)

application.properties:

dapr.client.httpEndpoint=http://localhost
dapr.client.httpPort=3500
dapr.client.grpcEndpoint=localhost
dapr.client.grpcPort=50001
dapr.statestore.name=statestore
dapr.pubsub.name=pubsub
Code-Sprache: JavaScript (javascript)

State Management: Persistieren ohne Datenbank-Treiber

Dapr abstrahiert State Stores (Redis, PostgreSQL, CosmosDB, etc.) — dein Code bleibt datenbank-agnostisch:

import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.State;

<em>// Client bauen (in Spring Boot per @Autowired verfügbar)</em>
DaprClient client = new DaprClientBuilder().build();

<em>// Speichern</em>
client.saveState("statestore", "order-42", new Bestellung("42", "Monitor", 349.0))
      .block();

<em>// Lesen</em>
State<Bestellung> state = client.getState("statestore", "order-42", Bestellung.class)
      .block();
Bestellung b = state.getValue();

<em>// Löschen</em>
client.deleteState("statestore", "order-42").block();
Code-Sprache: JavaScript (javascript)

Der Standard-Komponenten-Name statestore wird in ~/.dapr/components/statestore.yaml definiert und zeigt auf Redis. In Produktion änderst du nur diese YAML-Datei:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
spec:
  type: state.redis
  version: v1
  metadata:
  - name: redisHost
    value: redis-prod.meinefirma.com:6379

Service Invocation: Services aufrufen ohne Service Discovery

<em>// Service-Aufruf mit HTTP</em>
DaprInvokeHttpClient invoker = client.invokeHttpClient("payment-service");

HttpRequest request = invoker.newRequestBuilder("pay")
    .header("Content-Type", "application/json")
    .POST(DaprBodyPublishers.json(new Zahlung("order-42", 349.0)))
    .build();

HttpResponse<String> response = invoker.send(request,
    HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Code-Sprache: JavaScript (javascript)

Der Aufruf geht an payment-service — ohne feste Host/Port-Konfiguration. Dapr übernimmt Service Discovery (via mDNS lokal, Kubernetes-DNS im Cluster), mTLS und automatische Retries.

Pub/Sub: Entkoppelte Services via Events

<em>// Producer: Event publizieren</em>
client.publishEvent("pubsub", "bestellungen", new BestellungAufgegeben("order-42"))
      .block();
Code-Sprache: JavaScript (javascript)
<em>// Consumer: Event empfangen via REST-Endpunkt</em>
import io.dapr.Topic;
import org.springframework.web.bind.annotation.*;

@RestController
public class BestellungSubscriber {

    @Topic(name = "bestellungen", pubsubName = "pubsub")
    @PostMapping("/bestellungen")
    public void onBestellung(@RequestBody CloudEvent<BestellungAufgegeben> event) {
        BestellungAufgegeben data = event.getData();
        System.out.println("Bestellung empfangen: " + data.bestellnummer());
    }
}
Code-Sprache: JavaScript (javascript)

Dapr liefert Events im CloudEvents-Format — sprach- und plattformunabhängig. Der Consumer ist ein simpler REST-Endpunkt; Dapr managed Subscriptions, At-Least-Once-Delivery und Retries.

Secrets Management: Keine Secrets im Code

Map<String, String> secrets = client.getSecret("secretstore", "db-password").block();
String password = secrets.get("db-password");
<em>// App-Code enthält keine Credentials — sie kommen aus dem Sidecar</em>
Code-Sprache: JavaScript (javascript)

Dapr unterstützt AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Kubernetes Secrets und HashiCorp Vault als Backends.

Komplette Beispiel-Anwendung

@SpringBootApplication
@RestController
public class OrderService {
    public static void main(String[] args) {
        SpringApplication.run(OrderService.class, args);
    }

    @Autowired private DaprClient daprClient;

    @PostMapping("/orders")
    public ResponseEntity<String> createOrder(@RequestBody NewOrderRequest req) {
        <em>// 1. State speichern</em>
        Bestellung order = new Bestellung(UUID.randomUUID().toString(),
            req.product(), req.amount());
        daprClient.saveState("statestore", order.id(), order).block();

        <em>// 2. Payment-Service aufrufen</em>
        DaprInvokeHttpClient paymentClient =
            daprClient.invokeHttpClient("payment-service");
        HttpRequest paymentReq = paymentClient.newRequestBuilder("pay")
            .POST(DaprBodyPublishers.json(new ZahlungRequest(order.id(), order.amount())))
            .build();
        paymentClient.send(paymentReq, HttpResponse.BodyHandlers.ofString());

        <em>// 3. Event publizieren</em>
        daprClient.publishEvent("pubsub", "order-created",
            new OrderCreatedEvent(order.id())).block();

        return ResponseEntity.ok("Bestellung " + order.id() + " angelegt");
    }
}

record Bestellung(String id, String product, double amount) {}
record NewOrderRequest(String product, double amount) {}
record ZahlungRequest(String orderId, double amount) {}
record OrderCreatedEvent(String orderId) {}
Code-Sprache: JavaScript (javascript)

Dapr im Kubernetes-Cluster

Im Kubernetes-Cluster wird der Dapr-Sidecar automatisch per Annotation injiziert:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  annotations:
    dapr.io/enabled: "true"
    dapr.io/app-id: "order-service"
    dapr.io/app-port: "8080"
Code-Sprache: JavaScript (javascript)

Keine Container-Definition nötig — der Dapr-Sidecar-Container wird per Mutating Webhook angefügt.

Testcontainers für lokale Entwicklung

import io.dapr.testcontainers.DaprContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
class OrderServiceTest {

    @Container
    static DaprContainer dapr = new DaprContainer("daprio/daprd:1.18.0")
        .withAppName("order-service")
        .withAppPort(8080);

    @Test
    void testWithDapr() {
        <em>// Dapr-API steht unter dapr.getHttpEndpoint() zur Verfügung</em>
    }
}
Code-Sprache: JavaScript (javascript)

Wann lohnt sich Dapr?

Dapr ist kein Allheilmittel, aber in diesen Szenarien besonders wertvoll:

  • Multi-Sprache/Multi-Team — einheitliche Infrastruktur-API für Java, Go, Python, JS
  • Polyglotte Infrastruktur — Wechsel von Redis zu PostgreSQL → nur YAML ändern
  • Sicherheitsanforderungen — mTLS, Secrets, kein Credential im App-Code
  • On-Premise zu Cloud — gleicher Code, andere Komponenten-Konfiguration

Der Trade-off: Ein zusätzlicher Sidecar-Prozess pro Pod, und die Kommunikation läuft immer über localhost → Sidecar → Sidecar → localhost. Für latenzkritische Pfade kann dieser Hop relevant sein.

Fazit

Dapr ist ein Game-Changer für Infrastruktur-lastige Microservice-Landschaften. Es entkoppelt Business-Logik von Infrastruktur-Code und macht deine Services portabel zwischen Cloud-Providern und On-Premise. Das Java SDK 1.17.4 macht die Integration in Spring Boot fast unsichtbar.

Wenn dein nächstes Projekt mehr als zwei Services und mehr als einen Infrastruktur-Dienst (DB, Queue, Secrets) braucht: Gib Dapr einen Tag Testzeit — es könnte dir Monate Entwicklungsarbeit sparen.