Migration Deep Dive für 03 · REST öffnen →
⌂ ÜbersichtAlle Vergleiche
Modul 03 · 100 % Codeabdeckung

REST

Vollständiger Legacy-Modern-Vergleich aller Java-Klassen, Tests, Maven-POMs und relevanten Ressourcen dieses Moduls. Jede Datei ist genau einmal einer fachlich passenden Vergleichsgruppe zugeordnet.

6Legacy-Dateien
8Modern-Dateien
167 → 171Codezeilen
15/15vollständig erfasst

Fachliche Einordnung

Legacy

JAX-RS Resource mit javax.ws.rs

Die JAX-RS-Ressource greift direkt auf ein Singleton-Repository zu.

Modern

Spring MVC REST Controller

Der Controller delegiert über Constructor Injection an eine Service Layer.

Vergleichsfokus: Alle Dateien werden nach technischer Rolle und fachlicher Verantwortung gruppiert.
Wichtige Abgrenzung: One-to-many-Änderungen werden als Gruppe dargestellt; eine künstliche 1:1-Zuordnung wird vermieden.

Vollständige Abdeckungsmatrix

Die Ähnlichkeit ist eine technische Orientierung auf normalisiertem Quelltext. Sie ersetzt keine fachliche Bewertung.

VergleichsgruppeLegacy-DateienModern-DateienLegacy-ZeilenModern-ZeilenÄhnlichkeit
Build-Konfiguration11464635%
Bootstrap und Laufzeitkonfiguration12211321%
Domänenmodell1129622%
Datenzugriff21432854%
HTTP-Adapter, Service und Test1328784%

Gemeinsame Modulstruktur

Das Pair-POM bindet die Legacy- und Modern-Submodule zusammen und gehört deshalb ebenfalls zur vollständigen Codeabdeckung.

Gemeinsam · 19 Zeilenpom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>at.aydin.academy</groupId>
        <artifactId>java-modernization-deep-dive</artifactId>
        <version>1.0.0</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <artifactId>03rest-pair</artifactId>
    <packaging>pom</packaging>
    <name>REST: Legacy und Modern</name>
    <modules>
        <module>legacy</module>
        <module>modern</module>
    </modules>
</project>

Alle semantischen Codevergleiche

01Build-KonfigurationDirektvergleich · Legacy 46 / Modern 46 Zeilen · Ähnlichkeit 35%

JAX-RS- und Spring-Web-Abhängigkeiten werden verglichen.

Legacy-DateienModern-DateienEinordnung
pom.xmlpom.xmlDirektvergleich
Semantische Ähnlichkeit: 35%

Legacy

Legacy · 46 Zeilenlegacy/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>at.aydin.academy</groupId>
        <artifactId>03rest-pair</artifactId>
        <version>1.0.0</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>rest-legacy</artifactId>
    <packaging>jar</packaging>
    <name>REST Legacy (JAX-RS 2.1)</name>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-grizzly2-http</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>

        </plugins>
    </build>
</project>

Modern

Modern · 46 Zeilenmodern/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>at.aydin.academy</groupId>
        <artifactId>03rest-pair</artifactId>
        <version>1.0.0</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>rest-modern</artifactId>
    <packaging>jar</packaging>
    <name>REST Modern (Spring Boot)</name>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
02Bootstrap und LaufzeitkonfigurationDirektvergleich · Legacy 21 / Modern 13 Zeilen · Ähnlichkeit 21%

Application-Setup wird auf Spring Boot und externe Konfiguration umgestellt.

Legacy-DateienModern-DateienEinordnung
RestLegacyApplication.javaRestModernApplication.java
application.properties
Direktvergleich
Semantische Ähnlichkeit: 21%

Legacy

package at.aydin.lab.rest.legacy;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import java.net.URI;

public final class RestLegacyApplication {
    private RestLegacyApplication() {
    }

    public static void main(String[] args) throws Exception {
        ResourceConfig config = new ResourceConfig(BookResource.class, JacksonFeature.class);
        HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:8081/api/"), config);
        System.out.println("Legacy JAX-RS: http://localhost:8081/api/books");
        System.out.println("ENTER beendet den Server.");
        System.in.read();
        server.shutdownNow();
    }
}

Modern

package at.aydin.lab.rest.modern;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestModernApplication {
    public static void main(String[] args) {
        SpringApplication.run(RestModernApplication.class, args);
    }
}
03DomänenmodellDirektvergleich · Legacy 29 / Modern 6 Zeilen · Ähnlichkeit 22%

Request-/Response-Modell wird direkt gegenübergestellt.

Legacy-DateienModern-DateienEinordnung
Book.javaBook.javaDirektvergleich
Semantische Ähnlichkeit: 22%

Legacy

package at.aydin.lab.rest.legacy;

public class Book {
    private long id;
    private String title;
    public Book() {
    }

    public Book(long id, String title) {
        this.id = id;
        this.title = title;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

Modern

04DatenzugriffDirektvergleich · Legacy 43 / Modern 28 Zeilen · Ähnlichkeit 54%

Singleton-Repository wird als injizierbare Komponente verwendet; Testverantwortung verschiebt sich.

Legacy-DateienModern-DateienEinordnung
BookRepository.java
BookRepositoryTest.java
BookRepository.javaDirektvergleich
Semantische Ähnlichkeit: 54%

Legacy

package at.aydin.lab.rest.legacy;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

// Design Pattern: Repository
// Zweck: Datenhaltung wird von der REST-Ressource getrennt.
public final class BookRepository {
    private static final BookRepository INSTANCE = new BookRepository();
    private final ConcurrentHashMap<Long, Book> books = new ConcurrentHashMap<>();
    private final AtomicLong sequence = new AtomicLong();
    private BookRepository() {
        save(new Book(0, "Enterprise Java verstehen"));
    }

    public static BookRepository instance() {
        return INSTANCE;
    }

    public List<Book> findAll() {
        return new ArrayList<>(books.values());
    }

    public Book save(Book book) {
        long id = book.getId() == 0 ? sequence.incrementAndGet() : book.getId();
        Book stored = new Book(id, book.getTitle());
        books.put(id, stored);
        return stored;
    }
}
package at.aydin.lab.rest.legacy;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;

class BookRepositoryTest {
    @Test
    void containsSeedBook() {
        assertFalse(BookRepository.instance().findAll().isEmpty());
    }
}

Modern

package at.aydin.lab.rest.modern;

import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Repository
// Design Pattern: Repository
// Zweck: Der Controller kennt keine Details der Datenhaltung.
public class BookRepository {
    private final ConcurrentHashMap<Long, Book> books = new ConcurrentHashMap<>();
    private final AtomicLong sequence = new AtomicLong();
    public BookRepository() {
        save(new Book(0, "Modernes Enterprise Java"));
    }

    public List<Book> findAll() {
        return List.copyOf(books.values());
    }

    public Book save(Book input) {
        long id = input.id() == 0 ? sequence.incrementAndGet() : input.id();
        Book stored = new Book(id, input.title());
        books.put(id, stored);
        return stored;
    }
}
05HTTP-Adapter, Service und TestDirektvergleich · Legacy 28 / Modern 78 Zeilen · Ähnlichkeit 4%

Eine gekoppelte Resource wird in Controller, Service und Webtest aufgeteilt.

Legacy-DateienModern-DateienEinordnung
BookResource.javaBookController.java
BookService.java
BookControllerTest.java
Direktvergleich
Semantische Ähnlichkeit: 4%

Legacy

package at.aydin.lab.rest.legacy;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.List;

@Path("books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
    private final BookRepository repository = BookRepository.instance();
    @GET
    public List<Book> all() {
        return repository.findAll();
    }

    @POST
    public Response create(Book book) {
        Book created = repository.save(book);
        return Response.created(URI.create("/api/books/" + created.getId())).entity(created).build();
    }
}

Modern

package at.aydin.lab.rest.modern;

import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;

@RestController
@RequestMapping("/api/books")
public class BookController {
    private final BookService service;
    public BookController(BookService service) {
        this.service = service;
    }

    @GetMapping
    public List<Book> all() {
        return service.findAll();
    }

    @PostMapping
    public ResponseEntity<Book> create(@Valid @RequestBody Book book, UriComponentsBuilder uriBuilder) {
        Book created = service.create(book);
        return ResponseEntity.created(uriBuilder.path("/api/books/{id}").build(created.id())).body(created);
    }
}
package at.aydin.lab.rest.modern;

import org.springframework.stereotype.Service;
import java.util.List;

@Service
// Design Pattern: Service Layer
// Zweck: Fachlogik bleibt unabhängig von HTTP.
public class BookService {
    private final BookRepository repository;
    public BookService(BookRepository repository) {
        this.repository = repository;
    }

    public List<Book> findAll() {
        return repository.findAll();
    }

    public Book create(Book book) {
        return repository.save(book);
    }
}
package at.aydin.lab.rest.modern;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(BookController.class)
class BookControllerTest {
    @Autowired
    MockMvc mockMvc;
    @MockitoBean
    BookService service;
    @Test
    void returnsBooks() throws Exception {
        when(service.findAll()).thenReturn(List.of(new Book(1, "Testbuch")));
        mockMvc.perform(get("/api/books")).andExpect(status().isOk()).andExpect(jsonPath("$[0].title").value("Testbuch"));
    }
}
⌂ Cockpit