Skip to content

Complete .NET Framework → Java 21 / Spring Boot 3.5 Migration: eShopLegacyMVC#100

Open
devin-ai-integration[bot] wants to merge 89 commits into
mainfrom
migration/complete-java-migration-v3
Open

Complete .NET Framework → Java 21 / Spring Boot 3.5 Migration: eShopLegacyMVC#100
devin-ai-integration[bot] wants to merge 89 commits into
mainfrom
migration/complete-java-migration-v3

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented May 26, 2026

Copy link
Copy Markdown

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

  • 9 epics, 40 tickets executed across 7 waves of parallel child Devin sessions
  • 40 Java source files, 13 test files, 93 resource/config files (147 total)
  • 102 unit/integration tests — all passing, JaCoCo 80%+ coverage enforced

Architecture Mapping

.NET Component Spring Boot Equivalent
ASP.NET MVC 5 Controllers @Controller + @RestController
Entity Framework 6 Spring Data JPA + JpaRepository
Razor .cshtml Views Thymeleaf .html Templates
Autofac DI Spring @Configuration + @Bean
log4net SLF4J + Logback
Application Insights Actuator + Micrometer
BinaryFormatter Jackson JSON
Static vendor JS/CSS WebJars (Bootstrap, jQuery, Popper.js, Font Awesome)

Wave Execution Summary

Wave Epic PR(s)
0 Epic 0: Project Scaffolding #59
1 Epic 1: Domain Entities + Epic 4: Shared Library #64, #65
2 Epic 2: Data Access Layer #71
3 Epic 3: Service Layer #76
4 Epic 5: Controllers + Views #80
5 Epic 6: Cross-Cutting Concerns direct merge
6 Epic 7: Testing + Epic 8: Deployment #99, #96

Quality Gates (all passing)

  • mvn clean compile — PASS
  • mvn verify — PASS (102 tests, 0 failures)
  • JaCoCo 80%+ line coverage — PASS
  • /actuator/health — UP
  • All 13 API endpoints — PASS
  • All 5 UI pages render correctly — PASS

See MIGRATION_TEST_REPORT.md for the full test report.

Screenshots

Catalog Listing
Catalog listing page

Item Details
Item details page

Create Form
Create form

Edit Form
Edit form

Delete Confirmation
Delete confirmation

Review & Testing Checklist for Human

  • Run cd eShopLegacyMVC-SpringBoot && ./mvnw clean verify — expect 102 tests, 0 failures, JaCoCo check met
  • Run ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev and verify http://localhost:8080 shows the catalog with 12 products and images
  • Navigate to Create, Edit, Delete flows and verify forms submit correctly
  • Verify /actuator/health returns {"status":"UP"}
  • Review DEPLOYMENT.md and CUTOVER.md against your Azure VM setup and organizational processes
  • Verify GitHub Actions workflow (.github/workflows/deploy.yml) matches your CI/CD requirements and required secrets are documented

Notes

  • The app uses H2 in-memory database in dev/test profiles. For production, configure SPRING_DATASOURCE_URL pointing to SQL Server (mssql-jdbc driver is included).
  • Vendor CSS/JS (Bootstrap, jQuery, Font Awesome) are served via WebJars — no raw vendor files are committed.
  • The Dockerfile uses a multi-stage build with eclipse-temurin:21-jdk (build) → eclipse-temurin:21-jre-alpine (runtime).
  • Flyway migrations in 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


Open in Devin Review

- 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
@pattern only applies to CharSequence types. Using it on BigDecimal
causes UnexpectedTypeException at runtime. The @DecimalMin, @DecimalMax,
and @digits annotations already cover the intended validation.
…-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
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
- 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
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant