#Tests, Modernisierungsmuster und Refactoring-Regeln

Dieses Kapitel beschreibt Schutznetz, Charakterisierungstests, Ports-and-Adapters, Outbox und Entscheidungslogik pro Komponente.

#Tests und Schutznetz

#Reihenfolge

text
1. Smoke Test
2. Charakterisierungstest
3. Integrationstest
4. Contract Test
5. Regressionstest für Bugs
6. Test für extrahierte Use Cases

#Smoke Test

Ziel:

text
Kann die App starten?
Kann Login funktionieren?
Kann Hauptseite geladen werden?
Kann ein einfacher Use Case laufen?

#Charakterisierungstest

Ein Charakterisierungstest dokumentiert bestehendes Verhalten, auch wenn es unschön ist.

java
@Test
void auditIsCommittedEvenWhenOrderCreationFails() {
    try {
        orderService.createOrderThatFailsAfterAudit();
    } catch (Exception ignored) {
    }

    assertTrue(auditRepository.contains("ORDER_CREATED"));
    assertFalse(orderRepository.exists(orderId));
}

#SOAP Golden Master

text
src/test/resources/golden/customer-update-request.xml
src/test/resources/golden/customer-update-response.xml

Teste XML semantisch:

  • Whitespace ignorieren
  • Namespaces beachten
  • dynamische Felder ignorieren
  • Zeitstempel und IDs normalisieren

#JMS Handler Test

Statt MDB-Container zu testen, Handler extrahieren:

java
@Test
void duplicateOrderCreatedMessage_doesNotCreateSecondInvoice() {
    handler.handle(message);
    handler.handle(message);

    assertEquals(1, invoiceRepository.countByOrderId(orderId));
}

#Modernisierungsmuster

#Ports and Adapters

Port:

java
public interface OrderEventPublisher {
    void publishOrderCreated(OrderId orderId);
}

Legacy Adapter:

java
public class JmsOrderEventPublisher implements OrderEventPublisher {

    private final QueueSender sender;

    public JmsOrderEventPublisher(QueueSender sender) {
        this.sender = sender;
    }

    @Override
    public void publishOrderCreated(OrderId orderId) {
        sender.send("ORDER_CREATED", orderId.value());
    }
}

#Use Case extrahieren

java
public class CreateOrderUseCase {

    private final OrderRepository orderRepository;
    private final AuditPort auditPort;
    private final OrderEventPublisher eventPublisher;

    public CreateOrderUseCase(
            OrderRepository orderRepository,
            AuditPort auditPort,
            OrderEventPublisher eventPublisher
    ) {
        this.orderRepository = orderRepository;
        this.auditPort = auditPort;
        this.eventPublisher = eventPublisher;
    }

    public OrderId execute(CreateOrderCommand command) {
        Order order = Order.create(command.customerId(), command.items());
        orderRepository.save(order);
        auditPort.orderCreated(order.id());
        eventPublisher.publishOrderCreated(order.id());
        return order.id();
    }
}

#Anti-Corruption Layer

Externe SOAP/JAXB-Klassen nicht in die Domain lassen.

java
public ApprovalCommand toCommand(CustomerApprovalSoapType soapType) {
    return new ApprovalCommand(
            new CustomerId(soapType.getCustomerNo()),
            "Y".equals(soapType.getApprovedFlag())
    );
}

#Outbox Pattern

Problem:

java
@Transactional
public void createOrder() {
    orderRepository.save(order);
    jmsPublisher.publish(new OrderCreatedEvent(order.id()));
}

Besser:

text
In derselben DB-Transaktion:
- Order speichern
- Outbox Event speichern

Nach Commit:
- Publisher liest Outbox
- sendet JMS/Kafka/etc.
- markiert Event als published

Tabelle:

sql
CREATE TABLE outbox_event (
    id VARCHAR(36) PRIMARY KEY,
    aggregate_id VARCHAR(36),
    event_type VARCHAR(100),
    payload CLOB,
    status VARCHAR(20),
    created_at TIMESTAMP,
    published_at TIMESTAMP
);

#Entscheidungslogik pro Komponente

Nutze diese Strategien:

text
KEEP     → vorerst lassen
WRAP     → hinter Interface kapseln
EXTRACT  → Fachlogik in Plain Java ziehen
REPLACE  → Technologie ersetzen
DELETE   → ungenutzten Code entfernen
SPLIT    → fachlich trennen

Vorlage:

markdown
# Modernization Candidate: <Name>

## Fachlicher Zweck

## Aktuelle technische Form
- EJB / JSP / SOAP / JMS / DAO / Batch:
- Klasse:
- Modul:
- Deployment-Artefakt:

## Entry Points
- Wird aufgerufen von:
- Ruft selbst auf:

## Transaktion
- TransactionAttribute:
- Nested TX:
- Rollback-Regeln:
- XA beteiligt:

## Side Effects
- DB Writes:
- JMS Sends:
- SOAP Calls:
- Files:
- Emails:

## Risiken

| Risiko | Beschreibung | Schwere |
|---|---|---|
| Transaktion | ... | hoch |
| Messaging | ... | mittel |

## Tests vorhanden
- Unit Tests:
- Integration Tests:
- Manuelle Tests:
- Logs/Monitoring:

## Empfohlene Strategie
- KEEP / WRAP / EXTRACT / REPLACE / DELETE / SPLIT

## Erster sicherer Schritt

## Nicht anfassen ohne Test
- Transaktion
- Exception Handling
- SOAP Payload
- Queue Semantik
- Security

#Checkliste vor jedem Refactoring

Vor Änderung:

  • Habe ich den Entry Point verstanden?
  • Habe ich die Call Chain dokumentiert?
  • Kenne ich die Transaktionsgrenze?
  • Kenne ich alle DB Writes?
  • Kenne ich alle JMS Sends?
  • Kenne ich alle SOAP Calls?
  • Kenne ich Exception/Rollback-Verhalten?
  • Gibt es REQUIRES_NEW?
  • Gibt es NOT_SUPPORTED?
  • Gibt es Security Context?
  • Gibt es JNDI Lookups?
  • Gibt es mindestens Smoke/Charakterisierungstest?
  • Habe ich einen Rollback-Plan?

Nach Änderung:

  • Build erfolgreich?
  • Deployment erfolgreich?
  • Smoke Test erfolgreich?
  • Haupt-Use-Case erfolgreich?
  • Logs unauffällig?
  • Transaktion unverändert?
  • SOAP Payload unverändert?
  • JMS Destination unverändert?
  • DB Schema unverändert?
  • Exception-Verhalten unverändert?