Add modernization demo scaffold: React + Spring Boot replacing COBOL/CICS card management#170
Add modernization demo scaffold: React + Spring Boot replacing COBOL/CICS card management#170devin-ai-integration[bot] wants to merge 5 commits into
Conversation
…CICS card management - DEMO.md: CTO presentation script with architecture diagram and talking points - demo/frontend/: React SPA with CardListPage, CardDetailPage, CardUpdatePage, and GreenScreenPreview (3270 terminal mockup) components - demo/backend/: Spring Boot 3.x REST API with JPA entities (Card, Account, CardXref) mapped from COBOL copybooks, validation service with COBOL paragraph traceability, H2 database with seed data from mainframe files - demo/business-rules/: BRE documentation mapping COBOL programs to Java, screen-to-component mappings, and data model crosswalk - demo/README.md: Setup and run instructions - README.md: Added Modernization Demo section with links Co-Authored-By: Vedant Khanna <[email protected]>
| if (updated.getEmbossedName() != null && !updated.getEmbossedName().isBlank()) { | ||
| existing.setEmbossedName(updated.getEmbossedName().trim()); | ||
| } |
There was a problem hiding this comment.
🟡 Backend silently ignores blank embossedName instead of rejecting it
In CardService.updateCard() at line 42, a blank or null embossedName silently preserves the old value instead of returning a validation error. This diverges from the original COBOL 1230-EDIT-NAME paragraph which rejects blank names with an error message. While the React frontend validates name-not-blank, a direct API caller (e.g., PUT /api/cards/0500024453765740 with {"embossedName": "", "activeStatus": "Y", "expirationDate": "2023-03-09"}) would get a 200 OK response with the old name preserved, instead of a 400 error. The activeStatus and expirationDate fields both properly reject invalid values, making this inconsistent.
Was this helpful? React with 👍 or 👎 to provide feedback.
Backend: - Add name alpha-only validation (V-07): regex ^[A-Za-z ]+$ matching COBOL INSPECT CONVERTING - Add day=01 enforcement (V-09): EXPDAY field DRK,PROT convention - Add composite key verification (V-11): getCardByAccountAndCard() + /verify endpoint - Add @Version optimistic locking (D-05/D-06): equivalent to 9300-CHECK-CHANGE-IN-REC - Add @transactional on updateCard (D-05): equivalent to EXEC CICS READ UPDATE - Add account existence check (D-07): existsById() + FK constraints in schema - Add @JsonIgnore on cvvCode: prevent CVV exposure in API responses - Add GlobalExceptionHandler (@ControllerAdvice): handles validation, concurrency, abend errors - Add confirmation flag on update (N-03): equivalent to PF5 in 2000-DECIDE-ACTION - Add pagination support (N-04): 7-row pages matching WS-MAX-SCREEN-LINES Frontend: - Add alpha-only name validation in CardUpdatePage - Add day=01 validation with COBOL convention hint - Add confirmation modal dialog before save (PF5 equivalent) - Add pagination with PF7 Prev / PF8 Next buttons in CardListPage - Add account ID zero-padding on search (PIC 9(11) behavior) - Add informational messages (E-02): search results count, no-records-found Schema: - Add version column for optimistic locking - Add foreign key constraints (cards→accounts, xref→cards/accounts) - Add index on cards.account_id - Update seed data: dates use day=01, accounts inserted before cards Audit report regenerated: 28/30 rules covered, 2 partially, 0 missing.
| if (updated.getEmbossedName() != null && !updated.getEmbossedName().isBlank()) { | ||
| validateEmbossedName(updated.getEmbossedName()); | ||
| existing.setEmbossedName(updated.getEmbossedName().trim()); | ||
| } | ||
|
|
||
| // Equivalent to 1240-EDIT-CARDSTATUS in COCRDUPC.cbl | ||
| // Active status must be Y or N (FLG-YES-NO-VALID VALUES 'Y', 'N') | ||
| if (updated.getActiveStatus() != null) { | ||
| validateActiveStatus(updated.getActiveStatus()); | ||
| existing.setActiveStatus(updated.getActiveStatus()); | ||
| } | ||
|
|
||
| // Equivalent to 1250-EDIT-EXPIRY-MON + 1260-EDIT-EXPIRY-YEAR in COCRDUPC.cbl | ||
| if (updated.getExpirationDate() != null) { | ||
| validateExpirationDate(updated.getExpirationDate()); | ||
| existing.setExpirationDate(updated.getExpirationDate()); | ||
| } | ||
|
|
||
| // JPA @Version handles optimistic locking automatically on save(). | ||
| // If another transaction modified the record, OptimisticLockException is thrown, | ||
| // equivalent to 9300-CHECK-CHANGE-IN-REC detecting field-level changes. | ||
| return cardRepository.save(existing); |
There was a problem hiding this comment.
🔴 Optimistic locking is ineffective: client-provided version is discarded during update
In CardService.updateCard(), the method reads the latest existing card from the database and copies only embossedName, activeStatus, and expirationDate from the client-submitted updated card — but never copies or checks the version field. The client does receive and send back the version (the Card.version field has no @JsonIgnore, so the frontend receives it in CardUpdatePage.jsx:149 via setForm(res.data) and sends it back in the PUT at line 208), but the backend discards it.
This means the @Version optimistic lock only guards against truly concurrent in-flight database transactions (a very narrow race window within the @Transactional), not against the intended scenario where User A loads a card, User B updates it, then User A saves stale data — overwriting User B's changes silently. This defeats the purpose of rule D-06 / 9300-CHECK-CHANGE-IN-REC from the COBOL equivalent.
Concrete scenario showing silent data loss
- User A loads card (version=0)
- User B loads card (version=0)
- User B saves → DB version becomes 1
- User A saves →
findByIdreads version=1 from DB, copies A's form fields onto it, saves with version=1 → succeeds, version becomes 2 - User B's changes are silently overwritten
Prompt for agents
In CardService.updateCard(), the version field from the client-submitted Card (updated) is never compared against or applied to the existing entity before saving. This means JPA's @Version optimistic lock only protects against narrow in-flight transaction races, not the broader scenario where a user loaded data minutes ago and another user modified it in the meantime.
To fix this, after reading the existing card from the DB and before saving, set the version from the client onto the existing entity:
existing.setVersion(updated.getVersion());
This should be done after all field copies and before cardRepository.save(existing). This way, JPA will issue UPDATE ... WHERE version = <client_version>, which will fail with OptimisticLockingFailureException if the record was modified since the user loaded it. The GlobalExceptionHandler at config/GlobalExceptionHandler.java:49-58 already handles this exception and returns a 409 Conflict response, and the frontend at CardUpdatePage.jsx:214-215 already handles 409 status codes with an appropriate error message.
Alternatively, you can explicitly check: if updated.getVersion() != null and !updated.getVersion().equals(existing.getVersion()), throw an appropriate exception immediately.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Complete modernization demo: COBOL/CICS Card Management green screens → React + Spring Boot web application. Includes full demo scaffold (frontend, backend, BRE docs, presentation script) plus spec-driven BRE coverage improvements raising logic coverage from 68% → 93% (28/30 rules fully covered, 0 missing).
Key changes in latest iteration (BRE coverage fixes):
^[A-Za-z ]+$matching COBOLINSPECT CONVERTING/api/cards/{cardNumber}/verify?accountId=endpoint@Versionon Card entity +@TransactionalexistsById()+ FK constraints@JsonIgnoreGlobalExceptionHandler(@RestControllerAdvice) — structured error handling for validation (400), concurrency (409), and abend (500)Review & Testing Checklist for Human
http://localhost:3000, confirm 7 rows per page, click PF8 Next to see remaining 3 cards on page 2, click PF7 Prev to go backGET /api/cards/0500024453765740— response should NOT containcvvCodefieldNotes
cd demo/backend && mvn spring-boot:run(requires Java 17)cd demo/frontend && npm install && npm startdemo/business-rules/bre-audit-report.mdLink to Devin session: https://partner-workshops.devinenterprise.com/sessions/4b8ce2b840524f96a08a29f34912454b
Requested by: @VedantKh