JSP / JSF / Struts · Prio 8 · Vorher/Nachher

Legacy UI: Struts Action entkoppeln

Eine Action-Klasse enthält Session-State, Validierung, Berechtigungen, EJB-Aufruf und JSP-Modellaufbereitung. Das Refactoring trennt Controller, Form Validator, Presenter und Use Case.

← Refactoring Übersicht
Legacy UI: Struts Action entkoppeln Diagramm
Fachlich-technischer Schnitt vom Legacy-Problem zur refactorbaren Zielstruktur.
VorherStruts Action fett
Schritt 1Form Object
Schritt 2Presenter
Nachherdünner Controller
Problemverständnis
AspektBeschreibung
SymptomUI-Klasse entscheidet fachlich, ruft EJB direkt und füllt Request-Attribute mit Datenbank-/Legacy-Begriffen.
RisikoNeue UI oder REST API kann nicht wiederverwenden, weil Fachlogik im Web-Layer klebt.
Refactoring-ZielUI sammelt nur Eingabe, Presenter baut Anzeige, Use Case entscheidet fachlich.
MVCPresenterForm ObjectFacadeAuthorization PolicyAdapter
Vorher: Struts Action mit Fachlogik und Präsentationslogik
Warum kritisch: Dieser Code mischt Verantwortlichkeiten. Er ist nicht automatisch schlecht, aber Änderungen sind teuer, weil Fachlogik, Infrastruktur und Fehlerbehandlung verklebt sind.
Vorher: Struts Action mit Fachlogik und Präsentationslogik
public class LegacyOrderAction extends Action {
    public ActionForward execute(ActionMapping mapping, ActionForm form,
                                 HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpSession session = request.getSession(false);
        User user = (User) session.getAttribute("USER");
        LegacyOrderForm orderForm = (LegacyOrderForm) form;

        if (user == null || !user.hasRole("ORDER_WRITE")) {
            request.setAttribute("error", "Keine Berechtigung");
            return mapping.findForward("forbidden");
        }
        if (orderForm.getCustomerNo() == null || orderForm.getCustomerNo().trim().equals("")) {
            request.setAttribute("error", "Kunde fehlt");
            return mapping.findForward("input");
        }
        if (orderForm.getAmount() == null || new BigDecimal(orderForm.getAmount()).signum() <= 0) {
            request.setAttribute("error", "Betrag ungueltig");
            return mapping.findForward("input");
        }

        InitialContext ctx = new InitialContext();
        LegacyOrderBeanRemote bean = (LegacyOrderBeanRemote) ctx.lookup("ejb/LegacyOrderBean");
        LegacyOrderRequest legacy = new LegacyOrderRequest();
        legacy.setCustomerNo(orderForm.getCustomerNo());
        legacy.setAmount(new BigDecimal(orderForm.getAmount()));
        legacy.setPriority("on".equals(orderForm.getPriority()) ? "H" : "N");
        legacy.setUser(user.getLogin());

        LegacyOrderResponse legacyResponse = bean.createOrder(legacy);
        if (!"00".equals(legacyResponse.getReturnCode())) {
            request.setAttribute("error", legacyResponse.getReturnText());
            request.setAttribute("legacyCode", legacyResponse.getReturnCode());
            return mapping.findForward("input");
        }

        request.setAttribute("orderNo", legacyResponse.getOrderNo());
        request.setAttribute("successText", "Auftrag " + legacyResponse.getOrderNo() + " wurde angelegt");
        request.setAttribute("showPrintButton", user.hasRole("ORDER_PRINT"));
        return mapping.findForward("success");
    }
}
Form-Validator isoliert UI-Fehler ohne EJB-Start
Refactoring-Regel: Erst Verhalten sichern, dann Struktur ändern. Ohne Golden Master oder Characterization Test kann ein schönes Design fachlich falsch sein.
Form-Validator isoliert UI-Fehler ohne EJB-Start
class OrderFormValidatorTest {
    @Test
    void rejectsMissingCustomerBeforeCallingLegacySystem() {
        OrderFormInput input = new OrderFormInput("", "25.00", false);
        UserSession user = UserSession.withRoles("ORDER_WRITE");

        ValidationResult result = new OrderFormValidator().validate(input, user);

        assertThat(result.errorCodes()).contains("CUSTOMER_REQUIRED");
    }
}
Nachher: dünner Controller plus Presenter und Use Case
Warum besser: Der fachliche Ablauf ist erkennbar. Technische Details sitzen hinter Ports/Adaptern, Regeln sind benannt und Tests können ohne kompletten Legacy-Stack laufen.
Nachher: dünner Controller plus Presenter und Use Case
public class OrderAction extends Action {
    private final OrderFormValidator validator = new OrderFormValidator();
    private final OrderPresenter presenter = new OrderPresenter();
    private final OrderApplicationFacade facade = ServiceLocator.orderFacade(); // Pattern: Facade Adapter

    public ActionForward execute(ActionMapping mapping, ActionForm form,
                                 HttpServletRequest request, HttpServletResponse response) {
        UserSession user = UserSession.from(request);
        OrderFormInput input = OrderFormInput.from((LegacyOrderForm) form);

        ValidationResult validation = validator.validate(input, user); // Pattern: Form Object
        if (validation.hasErrors()) {
            presenter.presentInput(request, input, validation);
            return mapping.findForward("input");
        }

        CreateOrderResult result = facade.createOrder(input.toCommand(user.login()));
        OrderViewModel viewModel = presenter.toViewModel(result, user); // Pattern: Presenter
        presenter.write(request, viewModel);
        return mapping.findForward(viewModel.forwardName());
    }
}

public final class OrderPresenter {
    public OrderViewModel toViewModel(CreateOrderResult result, UserSession user) {
        if (result.rejected()) {
            return OrderViewModel.inputWithError(result.message(), result.legacyCode());
        }
        return OrderViewModel.success(
            "Auftrag " + result.orderNo() + " wurde angelegt",
            result.orderNo(),
            user.hasRole("ORDER_PRINT")
        );
    }
}
Wirkung und Trade-offs

UI modernisieren

Später kann React/Angular denselben Use Case nutzen.

Tests

Validierung und Presenter ohne Application Server testbar.

Sicherheit

Berechtigungsentscheidung wird explizit.

Lesbarkeit

JSP bekommt ein ViewModel statt Legacy-Rohdaten.

Wichtig: Refactoring heißt nicht Big-Bang-Neuschreiben. Die alte Schnittstelle darf stabil bleiben, während innen schrittweise sauberere Strukturen entstehen.
⌂ Cockpit