feat(sap-demo-java): add Customer 360 SAP S/4HANA + Postgres sample#126
feat(sap-demo-java): add Customer 360 SAP S/4HANA + Postgres sample#126slayerjain merged 3 commits intomainfrom
Conversation
A Spring Boot "Customer 360" API that exercises two integrations at once: - Outbound HTTPS to SAP S/4HANA Cloud Business Accelerator Hub (sandbox API Business Partner + Sales Order OData endpoints) - Local PostgreSQL persistence for customer tags, notes, and audit events The sample covers the full keploy flow (record + replay) against both egress surfaces simultaneously, and is structured to be driven from a Tosca-style flow script. It ships three run shapes: - docker-compose.yml: single-host demo (app + postgres) - k8s/ + kind-config.yaml + deploy_kind.sh: local kind cluster deploy - pom.xml mvn spring-boot:run: plain JVM Secrets handling: - .env is gitignored; only .env.example is committed (empty SAP_API_KEY) - k8s/secret.yaml is gitignored; only secret.yaml.example is committed (placeholder <PASTE_YOUR_SAP_API_KEY_HERE>) - Users populate both from their own SAP Business Accelerator Hub key Included flow artefacts: - demo_script.sh: end-to-end curl walkthrough - run_flow.sh / simulate_tosca_flow.sh: scripted user journeys - keploy.yml: recording config (empty globalNoise, no recorded data)
There was a problem hiding this comment.
Pull request overview
Adds a new sap-demo-java/ sample Spring Boot “Customer 360” service that aggregates SAP S/4HANA Business Partner data via outbound HTTPS while persisting local enrichment data (tags/notes/audit) in Postgres, intended for Keploy record/replay demos.
Changes:
- Introduces a Spring Boot Customer 360 API with SAP OData client, fan-out aggregator, RFC7807 error model, and correlation-id propagation.
- Adds Postgres schema + JPA repositories/entities for tags, notes, and audit events.
- Adds local + kind/K8s deployment assets and demo/flow scripts (docker-compose, manifests, deploy helpers, Keploy config).
Reviewed changes
Copilot reviewed 57 out of 57 changed files in this pull request and generated 22 comments.
Show a summary per file
| File | Description |
|---|---|
| sap-demo-java/src/test/java/com/tricentisdemo/sap/customer360/Customer360ApplicationTests.java | Context-load smoke test for Spring wiring |
| sap-demo-java/src/main/resources/logback-spring.xml | Logback config with kubernetes JSON profile |
| sap-demo-java/src/main/resources/db/migration/V1__init_schema.sql | Flyway schema for tags/notes/audit tables |
| sap-demo-java/src/main/resources/application.yml | App config: datasource, SAP, caching, actuator, logging |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/web/TagController.java | REST endpoints for local customer tags |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/web/NoteController.java | REST endpoints for local customer notes |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/web/GlobalExceptionHandler.java | RFC7807 problem responses + correlation headers |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/web/CustomerController.java | Customer list/detail/360/count endpoints |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/web/AuditController.java | REST endpoint for recent audit events |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/service/TagService.java | Tag persistence/business logic |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/service/NoteService.java | Note persistence/business logic |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/service/CustomerService.java | Simple SAP-backed customer queries |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/service/Customer360AggregatorService.java | Fan-out aggregator composing SAP + Postgres data |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/service/AuditService.java | Synchronous audit insert/read operations |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/sap/SapBusinessPartnerClient.java | SAP OData v2 gateway with caching + resilience |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/sap/SapApiException.java | Domain exception for SAP failures |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/sap/CorrelationIdInterceptor.java | Outbound correlation-id propagation to SAP |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/sap/CorrelationIdFilter.java | Inbound correlation-id seeding + response echo |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/repository/CustomerTagRepository.java | JPA repository for tags |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/repository/CustomerNoteRepository.java | JPA repository for notes |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/repository/AuditEventRepository.java | JPA repository for audit events |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/persistence/CustomerTag.java | Tag entity mapping |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/persistence/CustomerNote.java | Note entity mapping |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/persistence/AuditEvent.java | Audit event entity mapping |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/TagRequest.java | Tag request DTO + validation |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/ProblemResponse.java | RFC7807 problem DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/ODataEntityResponse.java | SAP OData v2 single-entity envelope DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/ODataCollectionResponse.java | SAP OData v2 collection envelope DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/NoteRequest.java | Note request DTO + validation |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/CustomerSummary.java | List projection DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/Customer360View.java | Aggregated 360 response DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/BusinessPartnerRole.java | SAP role DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/BusinessPartnerAddress.java | SAP address DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/model/BusinessPartner.java | SAP business partner DTO |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/config/SapClientConfig.java | RestTemplate + auth + executor configuration |
| sap-demo-java/src/main/java/com/tricentisdemo/sap/customer360/Customer360Application.java | Spring Boot entrypoint + OpenAPI metadata |
| sap-demo-java/simulate_tosca_flow.sh | Narrated Tosca-style flow script |
| sap-demo-java/run_flow.sh | Load-driving flow script (iterative calls) |
| sap-demo-java/pom.xml | Maven build + dependencies for the sample |
| sap-demo-java/kind-config.yaml | kind cluster config |
| sap-demo-java/keploy.yml | Keploy recording/replay configuration |
| sap-demo-java/k8s/service.yaml | NodePort service exposing the app |
| sap-demo-java/k8s/secret.yaml.example | Example secret template for SAP credentials |
| sap-demo-java/k8s/postgres.yaml | Postgres deployment/service/secret (demo) |
| sap-demo-java/k8s/namespace.yaml | Namespace definition + Keploy marker label |
| sap-demo-java/k8s/ingress.yaml | Ingress routing rules |
| sap-demo-java/k8s/deployment.yaml | App deployment with probes + security context |
| sap-demo-java/k8s/configmap.yaml | Non-secret runtime config |
| sap-demo-java/docker-compose.yml | Local compose runner for app + Postgres |
| sap-demo-java/deploy_kind.sh | kind cluster build/load/apply helper |
| sap-demo-java/demo_script.sh | Demo record/replay harness |
| sap-demo-java/README.md | Sample documentation and demo instructions |
| sap-demo-java/Dockerfile | Runtime container image packaging |
| sap-demo-java/.gitignore | Sample-local ignore rules (secrets/artifacts) |
| sap-demo-java/.env.example | Environment template for SAP credentials |
| sap-demo-java/.dockerignore | Docker build context filters |
| README.md | Top-level README: adds link to the new SAP demo |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); | ||
| factory.setConnectTimeout((int) Duration.ofSeconds(connectTimeoutSeconds).toMillis()); | ||
| factory.setConnectionRequestTimeout((int) Duration.ofSeconds(connectTimeoutSeconds).toMillis()); |
| * <p>Three interceptors are attached: | ||
| * <ol> | ||
| * <li><b>SapAuthInterceptor</b> — stamps the required {@code APIKey} header | ||
| * (sandbox) or {@code Authorization: Bearer} (production tenant).</li> | ||
| * <li><b>IdentityEncodingInterceptor</b> — forces {@code Accept-Encoding: identity} | ||
| * so bodies are not gzip-compressed in transit. In production this is | ||
| * optional; here it keeps Keploy-captured YAML mocks human-readable, | ||
| * which matters for demo-grade visibility and for diff-reviewing | ||
| * contract changes in pull requests.</li> | ||
| * <li><b>CorrelationIdInterceptor</b> — propagates the inbound request's | ||
| * correlation id into the outbound SAP call so traces chain across | ||
| * the hop.</li> | ||
| * </ol> |
| boolean deleted = tags.remove(customerId, tag); | ||
| audit.record(customerId, "tags.delete", null); | ||
| return ResponseEntity.ok(Map.of("deleted", deleted, "tag", tag.toLowerCase())); | ||
| } |
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ProblemResponse> handleUnexpected(Exception ex, HttpServletRequest req) { | ||
| log.error("Unhandled exception", ex); | ||
| ProblemResponse body = baseProblem(HttpStatus.INTERNAL_SERVER_ERROR, | ||
| "Internal Server Error", ex.getMessage(), req); | ||
| return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) | ||
| .headers(commonHeaders(null)) | ||
| .body(body); |
| log.warn("SAP error surfaced to caller: status={} upstream={} detail={}", | ||
| status.value(), ex.getUpstreamStatus().value(), ex.getMessage()); | ||
|
|
||
| return ResponseEntity.status(status) | ||
| .headers(commonHeaders(ex.getUpstreamStatus().value())) | ||
| .body(body); | ||
| } | ||
|
|
||
| @ExceptionHandler(CallNotPermittedException.class) | ||
| public ResponseEntity<ProblemResponse> handleCircuitOpen(CallNotPermittedException ex, | ||
| HttpServletRequest req) { | ||
| log.warn("Circuit breaker open, rejecting call: {}", ex.getMessage()); | ||
| ProblemResponse body = baseProblem( |
| # Uses amazoncorretto:25 — Amazon Linux 2023-based, minimal, JDK-ready. | ||
| # The jar is built outside Docker (mvn package); this image just packages it | ||
| # and runs it. K8s securityContext enforces the non-root runtime UID (1001). | ||
| # | ||
| # Docker Hub pull rate limits are avoided by relying on the locally cached | ||
| # amazoncorretto:25 image. | ||
|
|
||
| FROM amazoncorretto:25 |
| public ResponseEntity<ProblemResponse> handleUnexpected(Exception ex, HttpServletRequest req) { | ||
| log.error("Unhandled exception", ex); | ||
| ProblemResponse body = baseProblem(HttpStatus.INTERNAL_SERVER_ERROR, |
| } catch (TimeoutException e) { | ||
| log.warn("Aggregation timeout for bp={} after {}s — returning partial view", | ||
| customerId, aggregateTimeoutSeconds); | ||
| addresses = addressesF.getNow(List.of()); |
| ```bash | ||
| cd /home/shubham/tricentis/sap_testing/sap_demo_java | ||
|
|
||
| # 1. One-time: drop your SAP API key into .env | ||
| cp .env.example .env | ||
| $EDITOR .env # paste SAP_API_KEY | ||
|
|
||
| # 2. Stand everything up: kind cluster + build + load + apply |
| mockDownload: | ||
| registryIds: [] | ||
|
|
||
| # Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file. |
Applies 17 fixes from #126 review: Alignment / correctness: - Dockerfile: amazoncorretto:25 -> 21 (match pom java.version=21) - docker-compose.yml, k8s/postgres.yaml: postgres:15-alpine -> 16-alpine (align with README/PR "Postgres 16") - kind-config.yaml: add extraPortMappings for 30080 so the NodePort Service is reachable at http://localhost:30080 (previously 80/443 only) - SapClientConfig: plumb readTimeoutSeconds through RestTemplateBuilder (HttpComponentsClientHttpRequestFactory.setReadTimeout was removed in Spring 6 / HttpClient5) + drop Javadoc reference to the never-implemented IdentityEncodingInterceptor Bugs / robustness: - TagController.remove: trim + lowercase before echoing "tag" in response, so the response matches the value used by the service - GlobalExceptionHandler.handleUnexpected: stop leaking ex.getMessage() to clients; log full exception server-side with correlationId + path, return a generic body that invites the caller to quote the correlationId - TagService.add: replace exists() + findAll().stream().filter().orElseThrow() with a single findByCustomerIdAndTag Optional lookup, eliminating the race window + the unsafe orElseThrow(); CustomerTagRepository gains the matching finder - Customer360AggregatorService: split the combined `catch (InterruptedException | ExecutionException)` so Thread.currentThread().interrupt() fires only for InterruptedException - deploy_kind.sh build_and_load: replace `[ src -nt target/...jar ]` (which only compares the src directory mtime) with a `find src pom.xml -newer` sweep so rebuilds actually trigger after any file change Test / housekeeping: - Customer360ApplicationTests: add @AutoConfigureTestDatabase(Replace.ANY) + H2 test dep so the context-load smoke test runs without a live Postgres; disable Flyway (migrations are Postgres-specific) and let Hibernate generate the schema from entities at test time. Verified: context loads in ~5.6s locally - application.yml: drop logging.pattern.console — logback-spring.xml owns the pattern (and overrides Boot's key when present); avoids drift - keploy.yml: typo "configration" -> "configuration" - README.md Quick start: drop absolute /home/shubham/... path, use repo-relative `cd sap-demo-java` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per product direction, the e2e/sample path MUST exercise real Postgres + Flyway so Keploy is forced to correctly capture the full wire-protocol footprint — including the Flyway bootstrap/migration queries. Swapping the datasource for H2 at test time would hide exactly the behaviour the sample is meant to regress. Restores the Customer360ApplicationTests to its pre-review shape and drops the H2 test dependency. The test assumes a live Postgres on localhost:5432 (docker-compose.yml brings it up); added a Javadoc note so future readers don't re-H2 it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review round 2 — response to Copilot's 22 commentsPushed: `f2407c7` (review fixes) + `679fc8b` (review round 2). Applied (17)
Not applied — with rationaleCopilot's 4 WARN-level-logging complaints (`GlobalExceptionHandler:56`, `SapClientConfig:77`, `SapBusinessPartnerClient:223`, `Customer360AggregatorService:121`) cite "this repo's logging guideline" as prohibiting WARN. That guideline does not exist in this repo — there is no `.github/instructions/*`, no `CONTRIBUTING` rule, no `CODESTYLE` section that says so (verified: `find .github -type f` + `CONTRIBUTING.md`). The existing Java samples use WARN level freely. WARN is the correct level for the four sites flagged:
`pom.xml` Java 17 vs 21 — Copilot saw "Java 17" in my PR description. The canonical version is 21 (pom + Dockerfile agree; CI uses `eclipse-temurin-21`). Fixing the PR description. `k8s/service.yaml:18` — rolled up into the kind-config `extraPortMappings` fix (#4); no separate change to the Service needed. Re-requesting Copilot review. |
Summary
Adds a new sample app:
sap-demo-java/— a Spring Boot Customer 360 API that exercises two Keploy-recordable egress surfaces simultaneously:It is structured so a single end-to-end user journey drives a batch of HTTP + SAP OData + Postgres interactions, which is exactly the shape we want for recording/replaying real-world integration testcases.
What's in the sample
src/docker-compose.ymlDockerfilek8s/+kind-config.yaml+deploy_kind.shdemo_script.shrun_flow.sh/simulate_tosca_flow.shpom.xmlkeploy.ymlSecrets handling
All real credentials are gitignored and not committed:
.env— gitignored. Only.env.example(emptySAP_API_KEY) is committed.k8s/secret.yaml— gitignored. Onlyk8s/secret.yaml.example(placeholder<PASTE_YOUR_SAP_API_KEY_HERE>) is committed.Users supply their own free SAP Business Accelerator Hub sandbox API key (https://api.sap.com → any API → "Show API Key").
Why a new sample
The existing Java samples cover Spring Boot + a single data store (Postgres or Mongo).
sap-demo-javais the first that:postgresandhttpinterceptors in one flow.Test plan
cd sap-demo-java && cp .env.example .envand paste a sandbox API keydocker compose up --build→ app onlocalhost:8080/actuator/healthreturnsUPbash demo_script.sh→ walkthrough exercises SAP BP + local tag/note/audit CRUDbash deploy_kind.sh→ sample deploys to a local kind cluster (NodePort 30080)keploy record -c "docker compose up --build") + replay against the same flow produces a green test reportRelated
This sample is used as a regression fixture for the Postgres v3 parser in keploy/integrations, so changes to the parser are validated against a real enterprise-shape application.
🤖 Generated with Claude Code