Complete .NET Framework → Java 21 / Spring Boot 3.5 Migration: eShopLegacyMVC#100
Open
devin-ai-integration[bot] wants to merge 89 commits into
Open
Complete .NET Framework → Java 21 / Spring Boot 3.5 Migration: eShopLegacyMVC#100devin-ai-integration[bot] wants to merge 89 commits into
devin-ai-integration[bot] wants to merge 89 commits into
Conversation
- Create Maven project structure under eShopLegacyMVC-SpringBoot/ - Configure pom.xml with spring-boot-starter-parent 3.5.0 and Java 21 - Add starters: web, data-jpa, thymeleaf, validation, actuator - Add DB drivers: mssql-jdbc, h2 (runtime scope) - Add Flyway (core + sqlserver), Thymeleaf Layout Dialect, Micrometer Prometheus - Add test deps: spring-boot-starter-test, spring-security-test - Configure plugins: spring-boot, surefire, failsafe, jacoco, spotless - Create CatalogApplication.java entry point - Configure application.yml with Spring profiles (dev, mock, prod) - Map Web.config appSettings to Spring properties (useMockData, useCustomizationData) - Configure logback-spring.xml with profile-specific logging levels - Copy all static assets from .NET project (CSS, JS, images, fonts, pics, favicon) - Include Maven Wrapper (mvnw) - Verified: mvn clean compile passes without errors Resolves: NM-131, NM-132, NM-133
…t-scaffolding Epic 0: Initialize Spring Boot 3.5 project scaffold
- CatalogItem: @entity mapped to 'Catalog' table with @manytoone relationships, @transient PictureUri, BigDecimal Price with @Pattern/@Digits/@DecimalMin/@DecimalMax, no @GeneratedValue (HiLo) - CatalogBrand: @entity mapped to 'CatalogBrand' table with @GeneratedValue, @NotNull @SiZe(max=100) Brand - CatalogType: @entity mapped to 'CatalogType' table with @GeneratedValue, @NotNull @SiZe(max=100) Type, @column(name='Type') Implements NM-134, NM-135, NM-136
- Add JsonSerializationUtil as Spring @component with singleton ObjectMapper - serializeToJson/deserializeFromJson for JSON string serialization - serializeToBytes/deserializeFromBytes for binary-compatible serialization - ObjectMapper configured with JavaTimeModule for date/time support - Custom SerializationException wrapping Jackson exceptions - 12 unit tests covering round-trip, error handling, collections, and Java Time
…-entities Epic 1: Domain Entities + Validation
…-library Epic 4: Replace BinaryFormatter with Jackson JSON serialization
- NM-137: Create Spring Data JPA repositories for CatalogItem, CatalogBrand, CatalogType - CatalogItemRepository with paginated eager-fetch query - NM-138: Flyway V1 migration with H2-compatible schema (sequences, tables, FKs) - NM-139: Port CatalogItemHiLoGenerator with ReentrantLock and JdbcTemplate - NM-140: Flyway V2 seed data migration with all preconfigured data - NM-141: CSV-based DataInitializer (ApplicationRunner) for customization data - Copy CSV files from .NET Setup/ to Spring Boot resources/data/ - Update dev profile ddl-auto to validate (Flyway manages schema)
…ccess-layer Epic 2: Data Access Layer (Repositories + DB Initialization)
- Story 3.3: PaginatedItemsDto<T> generic DTO with calculated totalPages - Story 3.1: CatalogService interface (7 methods) and CatalogServiceImpl using Spring Data JPA repos, Pageable, @transactional, HiLo generator, and eager-fetch for findCatalogItem via new repository query - Story 3.2: CatalogServiceMock with in-memory list from PreconfiguredData - PreconfiguredData helper with 12 items, 5 brands, 4 types - @ConditionalOnProperty switches: mock=true (default), real=false - Added findByIdWithBrandAndType to CatalogItemRepository
…e-layer Epic 3: Service Layer
Port all controllers and Thymeleaf templates from ASP.NET MVC to Spring Boot: Controllers: - CatalogController: MVC controller with CRUD actions (Index, Details, Create, Edit, Delete) - PicController: Serves product images from classpath:/static/pics/ - BrandsRestController: REST API for catalog brands (GET all, GET by id, DELETE demo) - FilesRestController: REST API returning brands as JSON (replaces BinaryFormatter) - CatalogApiController: Simple REST API returning Hello World message Templates: - layout.html: Thymeleaf layout with header, hero section, footer using layout dialect - catalog/index.html: Paginated product table with navigation controls - catalog/create.html: Create product form with brand/type dropdowns and validation - catalog/edit.html: Edit product form with image preview - catalog/details.html: Read-only product details view - catalog/delete.html: Delete confirmation view - error.html: Generic error page Supporting: - GlobalModelAttributes: ControllerAdvice providing session info to all views
Add null guard for catalogService.findCatalogItem() result in deleteConfirmed, matching the pattern used in all other controller methods. Also remove unused RedirectAttributes import.
…llers-views Epic 5: Controllers + Views (Thymeleaf Templates)
Story 6.1 (NM-157): Replace Autofac DI with Spring @configuration - Add AppProperties with @ConfigurationProperties for app.useMockData/useCustomizationData - Add CatalogConfig @configuration with @EnableConfigurationProperties Story 6.2 (NM-158): Replace log4net with SLF4J + Logback - Verify all classes use SLF4J Logger (already in place from previous epics) - logback-spring.xml has profile-specific config (DEBUG for dev, INFO for prod) Story 6.3 (NM-159): Replace Application Insights with Actuator + Micrometer - Add CatalogMetrics with custom counters for items created/updated/deleted/viewed - Wire metrics into CatalogController for catalog operations - Expose health, info, metrics, prometheus, env actuator endpoints - Add common application tag for metrics Story 6.4 (NM-160): Port error handling and CSRF protection - Add GlobalExceptionHandler @ControllerAdvice with JSON responses for API endpoints - Add SecurityConfig with permitAll() and CSRF enabled for forms, disabled for /api/** - Add spring-boot-starter-security and thymeleaf-extras-springsecurity6 - Update error.html template with layout integration Story 6.5 (NM-161): Port session tracking (machine name, session start time) - Add SessionInfoInterceptor storing machineName/sessionStartTime in HTTP session - Add WebMvcConfig to register the interceptor - Update GlobalModelAttributes to expose separate machineName and sessionStartTime - Apply spotless formatting to existing files
- Revert populateDropdowns to use 'types'/'brands' matching template references - Remove env from actuator exposed endpoints to prevent leaking sensitive config
…cutting Epic 6: Cross-Cutting Concerns (DI, Logging, Telemetry, Error Handling)
- Dockerfile: multi-stage build (temurin:21-jdk build, temurin:21-jre-alpine run) with non-root user, health check, and layer caching optimization - .dockerignore: excludes build artifacts, IDE files, docs - docker-compose.yml: app service with SQL Server dependency - azure-pipelines.yml: Build & Test, Docker Build & Push, Deploy to Staging, Health Check, Deploy to Production stages - K8s manifests: Deployment (2 replicas, resource limits, health probes), Service (ClusterIP 80->8080), Ingress (nginx, TLS), ConfigMap, Secret, NetworkPolicy - CUTOVER.md: pre-cutover checklist, data migration steps, canary/DNS cutover procedure, rollback plan, monitoring checklist, success criteria Implements: NM-167, NM-168, NM-169, NM-170
…Co coverage - NM-162: JUnit 5 unit tests for CatalogServiceImpl (Mockito mocks) - NM-163: @WebMvcTest controller tests for CatalogController and PicController - NM-164: @SpringBootTest API integration tests for REST endpoints - NM-165: Smoke tests (context loads, /actuator/health, static assets, homepage) - NM-166: JaCoCo 80% line coverage enforcement on service and controller packages Test infrastructure: - application-test.yml with H2 in-memory DB and mock data profile - 81 tests, all passing - mvn verify passes with coverage checks met
Use actual schema table names (Catalog, CatalogBrand, CatalogType) matching V1__create_schema.sql and JPA entity annotations.
- Spring Boot 3.5.0 with Java 21 and Maven in eShopLegacyMVC-SpringBoot/ - pom.xml with all required starters, Flyway, MSSQL/H2 drivers, WebJars - WebJars: Bootstrap 4.3.1, jQuery 3.3.1, Popper 1.14.3, Font Awesome 5.15.4, jquery-validation 1.19.5 - CatalogApplication.java with @SpringBootApplication entry point - Package structure: config, controller, dto, model, repository, service, exception - Minimal application.yml placeholder (full config deferred to NM-132) - CatalogApplicationTests.java with contextLoads test - Maven Wrapper (mvnw) for reproducible builds - .gitignore excluding target/, *.class, .idea/, *.iml
Add base.css and custom.css to static/css/ for app-specific styles. All other static assets (Site.css, brand images, product images, favicon) already exist from prior commits. Vendor libraries (jQuery, Bootstrap, Popper, Modernizr, etc.) are managed via WebJars in pom.xml and are NOT included in static/.
- Replace base application.yml with proper Spring Boot config (JPA, Flyway, Thymeleaf, Actuator endpoints, app.catalog properties) - Update application-dev.yml with H2 in-memory DB, H2 console, database-platform, and logging levels - Update application-mock.yml with H2 DB and mock data flag under app.catalog namespace - Update application-prod.yml with SQL Server config using env var placeholders, database-platform, and logging levels - Replace logback-spring.xml with Spring Boot defaults-based config using profile-specific logging (dev/mock, prod, default) - Add CatalogProperties.java for type-safe @ConfigurationProperties binding of app.catalog settings - Remove DataSourceAutoConfiguration and FlywayAutoConfiguration exclusions from CatalogApplication to enable proper Spring Boot auto-configuration
…t-scaffolding Epic 0: Initialize Spring Boot 3.5 project scaffold
- Create AppConfig.java @configuration class with @bean definitions - CatalogService beans registered conditionally via @ConditionalOnProperty - CatalogServiceMock active when app.use-mock-data=true (default) - CatalogServiceImpl active when app.use-mock-data=false - CatalogItemHiLoGenerator registered as singleton @bean - Remove @Service/@component annotations from service/generator classes - All controllers continue to receive services via constructor injection Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add MdcLoggingFilter to port ActivityIdHelper and WebRequestInfo to MDC - Each request gets a unique activityId (UUID) for correlation - Request URL and User-Agent stored in requestInfo MDC key - Update logback-spring.xml pattern to include MDC fields - All controllers/services already use SLF4J LoggerFactory - Profile-aware logging: dev=DEBUG, prod=INFO (already configured) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ometer (#83) - Configure /actuator/health to show-details=always - Enable /actuator/info with app metadata (name, description, version) - Enable Java, OS, and build info contributors for /actuator/info - Add build-info goal to spring-boot-maven-plugin for build metadata - /actuator/metrics and /actuator/prometheus already exposed - CatalogMetrics already provides custom Micrometer counters - No Application Insights references remain in the codebase Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add ResourceNotFoundException for 404 errors - Add handler for NoResourceFoundException (Spring MVC 404s) - Enhance GlobalExceptionHandler with proper ModelAndView for MVC errors - Return JSON error responses for API requests, error.html for MVC requests - Add errorBody/errorView helper methods to reduce duplication - Disable whitelabel error page in favor of custom error.html template - Spring Security CSRF already enabled for form submissions - Thymeleaf forms already use th:action for automatic CSRF token inclusion Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Create SessionConfig with HttpSessionListener bean (ports .NET Session_Start) - Machine hostname stored in session on first request via sessionCreated - Session start time stored in session on first request - Update GlobalModelAttributes to read from HttpSession instead of instance fields - Values accessible in Thymeleaf layout footer via sessionInfo model attribute - Remove redundant SessionInfoInterceptor (replaced by SessionConfig listener) - Clean up WebMvcConfig (no interceptor registration needed) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Revert show-details to when-authorized in base application.yml - Set show-details=always only in application-dev.yml - Disable OS info contributor in application-prod.yml - Addresses Devin Review feedback on PR #83
…gument Addresses Devin Review feedback on PR #84
- Accept incoming DataInitializer and CatalogItem changes - Keep our @Service/@component removal in CatalogServiceImpl/CatalogServiceMock - CatalogItemHiLoGenerator moved to config package (incoming), @component removed (ours) - Update AppConfig import for new CatalogItemHiLoGenerator location - Merge logback-spring.xml: keep MDC pattern (ours) + mock profile (incoming) - Merge error.html: keep styled error page (ours) + dev error details (incoming)
- Multi-stage Dockerfile: build with eclipse-temurin:21-jdk, runtime with eclipse-temurin:21-jre-alpine - Build stage runs mvn package -DskipTests with dependency caching - Runtime stage copies JAR, exposes port 8080, runs as non-root user - Health check configured with curl against /actuator/health - Install curl in alpine runtime for health check support - .dockerignore excludes build output, IDE files, docs, CI/CD configs
AppConfig @ConditionalOnProperty and AppProperties @ConfigurationProperties were using 'app.use-mock-data' / prefix='app' but YAML defines the property under 'app.catalog.use-mock-data'. This caused CatalogServiceMock to always be registered (matchIfMissing=true) regardless of configuration. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Trigger on push to main and migration/complete-java-migration branches - Build & test job: JDK 21 setup, Maven cache, ./mvnw clean verify, Spotless check - Test results published via dorny/test-reporter - Docker build & push job: builds image and pushes to GHCR - Staging deploy job: SSH into staging VM via appleboy/ssh-action - Health check job: verifies /actuator/health returns UP on staging - Production deploy job: SSH-based deployment with GitHub Environment protection - Required secrets documented: STAGING_VM_HOST, PROD_VM_HOST, VM_USERNAME, VM_SSH_KEY, STAGING_URL, PROD_URL
- DEPLOYMENT.md: full deployment architecture docs (pipeline flow, GitHub Environments/Secrets, Azure VM prerequisites, env var config, DNS guidance) - scripts/setup-vm.sh: idempotent VM setup (Docker install, app dirs, env template) - docker-compose.yml: updated health check to use curl consistently with Dockerfile - .dockerignore already updated in NM-167 to exclude scripts/ and .github/
…nd CatalogServiceMock (#92) - Add sort order verification test for CatalogServiceImpl pagination - Add multiple item creation uniqueness test for CatalogServiceImpl - Add explicit repository delete verification test - Add beyond-last-page edge case test for CatalogServiceMock - Add known type/brand content assertion tests - Add non-existing item removal no-effect test - Add ID assignment verification for new items in CatalogServiceMock - All service methods covered: findCatalogItem, getCatalogBrands, getCatalogItemsPaginated, getCatalogTypes, createCatalogItem, updateCatalogItem, removeCatalogItem - CatalogServiceImpl uses @Mock/@Injectmocks with Mockito - CatalogServiceMock tested with in-memory preconfigured data - 34 total service layer unit tests, 100% line coverage on service layer Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Rewrite CUTOVER.md for Azure VM deployment (replaces K8s-focused version) - Rollback plan: steps to revert to .NET application - Cutover checklist: DNS switch, database migration verification, smoke tests - Database rollback strategy: Flyway undo migrations or backup/restore - Traffic switching: single-VM and blue-green (two VMs) approaches - Monitoring checklist: Actuator health, Micrometer/Prometheus metrics - Communication plan: stakeholder notifications before/during/after cutover - Fix: deploy.yml Docker image tag mismatch (use full SHA for GHCR tags)
Addresses Devin Review feedback on PR #91: script was hardcoded to create production.env but is documented to run on both staging and production VMs. Now accepts an argument to generate the correct env file. Updated DEPLOYMENT.md with per-environment usage examples.
- BrandsRestControllerTest: 6 tests covering GET /api/brands, GET /api/brands/{id},
DELETE /api/brands/{id} with happy paths and 404 error paths
- FilesRestControllerTest: 2 tests covering GET /api/files with JSON response
and empty brands scenario
- CatalogApiControllerTest: 2 tests covering GET /api with Hello World message
and JSON content type verification
- All tests use @WebMvcTest with @MockBean service dependencies
- SecurityConfig imported for proper CSRF exemption on API endpoints
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
) - @SpringBootTest(webEnvironment = RANDOM_PORT) with TestRestTemplate - H2 in-memory database via test profile - Tests for all REST endpoints: GET /api, GET/DELETE /api/brands, GET /api/brands/{id}, GET /api/files - MVC endpoint tests: GET /, GET /catalog/details/{id}, GET /catalog/create - Verifies HTTP status codes (200, 404), response body content, and Content-Type headers - 10 integration tests covering full request/response cycle Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- EndpointSmokeIT.java follows *IT.java naming convention for failsafe plugin - @SpringBootTest(webEnvironment = RANDOM_PORT) with TestRestTemplate - 14 smoke tests verifying 2xx responses on startup for all public endpoints: GET /actuator/health (verifies {"status": "UP"}) GET /, GET /api, GET /api/brands, GET /api/brands/{id} GET /api/files, GET /catalog/details/{id}, GET /catalog/create GET /catalog/edit/{id}, GET /catalog/delete/{id}, GET /items/{id}/pic DELETE /api/brands/{id}, GET /css/bootstrap.min.css, GET /favicon.ico - Tests run via 'mvn verify' through failsafe plugin Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Exclude entity classes (com/eshop/catalog/model/**) - Exclude DTOs (com/eshop/catalog/dto/**) - Exclude configuration classes (com/eshop/catalog/config/**) - Exclude CatalogApplication main class - Enforce 80% line coverage at BUNDLE level after exclusions - Move report generation to verify phase (generated during mvn verify) - Build fails if coverage drops below 80% threshold - Report available at target/site/jacoco/index.html Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- Add docker login to GHCR on remote VMs before docker pull (staging and production deploy jobs) using GITHUB_TOKEN via appleboy/ssh-action envs parameter - Fix DEPLOYMENT.md: docker compose with SQL Server requires SPRING_PROFILES_ACTIVE=prod to override mock profile defaults
…ment Epic 8: Deployment Configuration
…ebJar paths, error template
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full migration of the eShopLegacyMVC catalog management application from ASP.NET MVC 5 / .NET Framework 4.7.2 to Java 21 / Spring Boot 3.5.0.
Migration Scope
Architecture Mapping
@Controller+@RestControllerJpaRepository.cshtmlViews.htmlTemplates@Configuration+@BeanBinaryFormatterWave Execution Summary
Quality Gates (all passing)
mvn clean compile— PASSmvn verify— PASS (102 tests, 0 failures)/actuator/health— UPSee
MIGRATION_TEST_REPORT.mdfor the full test report.Screenshots
Catalog Listing

Item Details

Create Form

Edit Form

Delete Confirmation

Review & Testing Checklist for Human
cd eShopLegacyMVC-SpringBoot && ./mvnw clean verify— expect 102 tests, 0 failures, JaCoCo check met./mvnw spring-boot:run -Dspring-boot.run.profiles=devand verifyhttp://localhost:8080shows the catalog with 12 products and images/actuator/healthreturns{"status":"UP"}DEPLOYMENT.mdandCUTOVER.mdagainst your Azure VM setup and organizational processes.github/workflows/deploy.yml) matches your CI/CD requirements and required secrets are documentedNotes
SPRING_DATASOURCE_URLpointing to SQL Server (mssql-jdbc driver is included).eclipse-temurin:21-jdk(build) →eclipse-temurin:21-jre-alpine(runtime).src/main/resources/db/migration/handle schema creation and seed data.Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/e59c503f43bc4f7b8342d9ff6b9a1f78
Requested by: @mbatchelor81