Implementation Roadmap
Current focus: Phase 0 β PostgreSQL persistence layer
This is the living development plan for Transform Platform. Update task status and notes here as work progresses. The
PLAN.mdat the repo root is the authoritative copy β this page mirrors it for visibility.
Ground Truth: Where We Standβ
Code that actually exists and worksβ
| Module | Status | What's built |
|---|---|---|
platform-core | β Done | CSV / Fixed-Width / XML parsers, CorrectionEngine, ValidationEngine, TransformationPipeline, KafkaRecordWriter, ParserRegistry, unit tests |
platform-api | β οΈ Partial | SpecController, TransformController β in-memory HashMaps, no DB persistence, paths are /api/v1/... |
platform-common | β Empty | build.gradle.kts only |
platform-scheduler | β Empty | Quartz dependency declared, no code |
platform-pipeline | ποΈ To remove | Spring Batch β wrong model, replaced by Window/Action |
Docs ahead of code (our implementation target)β
These are designed and documented β the code needs to catch up:
- Window, Action, Profile domain β zero code
- Integration channels (SFTP, Kafka, DB, REST, S3) β zero code
- Events β File pipeline β zero code
- PostgreSQL persistence β driver declared, not wired
- Credential encryption β designed, zero code
- ~50 OpenAPI endpoints documented, ~7 implemented
Module Structure (Target)β
platform-common/ β shared JPA entities, DTOs, interfaces, exceptions
platform-core/ β transformation engine (parsers, correction, validation) β
DONE
platform-integration/ β integration channel implementations (SFTP, Kafka, DB, REST, S3) NEW
platform-scheduler/ β Window scheduler, Quartz jobs, WindowInstance state machine
platform-api/ β Spring Boot REST API (all controllers + services)
Dependency flow:
platform-api
βββ platform-scheduler β platform-integration β platform-common
βββ platform-core β platform-common
Phase 0 β Foundationβ
Goal: Replace in-memory stores with PostgreSQL. Everything else depends on this.
- Move domain entities to
platform-commonβFileSpecEntity,IntegrationEntity,CredentialEntityas JPA entities with JSONB columns for nested config - Add Flyway to
platform-apiβ migrationsV1__create_filespecs.sql,V2__create_integrations.sql,V3__create_credentials.sql - Wire
application.ymldatasource config and replaceHashMapinSpecServicewithFileSpecRepository - Fix API path prefix β align code from
/api/v1/β/api/to match the OpenAPI spec - Add
CredentialServiceβ AES-256-GCM encrypt/decrypt,POST/DELETE/PUT /api/credentials - Remove
platform-pipelinemodule (Spring Batch is the wrong model)
Deliverable: Running Spring Boot app that persists FileSpecs and credentials to PostgreSQL. All existing tests pass.
Phase 0.5 β Observability Foundationβ
Goal: Full three-pillar observability β logs, metrics, distributed traces β wired before Phase 1 so every domain event is instrumented from day one.
Stack:
| Signal | Tool | Port |
|---|---|---|
| Traces | Jaeger (via OTel Collector) | :16686 |
| Metrics | Prometheus β Grafana | :9090 / :3000 |
| Logs | Elasticsearch β Kibana | :9200 / :5601 |
Code changes only (no new modules):
-
platform-api/build.gradle.ktsβ addmicrometer-registry-prometheus,micrometer-tracing-bridge-otel,opentelemetry-exporter-otlp,logstash-logback-encoder -
application.ymlβ add OTel OTLP endpoint config + tracing sampling rate -
logback-spring.xmlβ new: structured JSON logs withtraceId,spanId,correlationIdin every line -
ObservabilityConfig.ktβ Micrometer common tags (service name, env) -
CorrelationIdFilter.ktβ inject + propagateX-Correlation-ID, set MDC per request -
TransformMetrics.ktβ custom business counters/timers for records processed, file duration, window events, action execution
Docker services to add:
-
otel-collectorβ central hub; receives OTLP from app, routes to Jaeger + Prometheus + Elasticsearch -
elasticsearchβ log storage (single-node, 512 MB heap for local dev) -
kibanaβ log dashboards -
prometheusβ metrics storage; scrapes app + OTel Collector -
grafanaβ metrics dashboards with auto-provisioned datasources + pre-built dashboard -
jaegerβ distributed tracing UI (all-in-one image)
New config files in .docker/:
-
otel-collector-config.yamlβ OTLP receivers β batch β Jaeger + Prometheus + ES exporters -
prometheus.ymlβ scrape configs for app and OTel Collector -
grafana/provisioning/datasources.ymlβ auto-wire Prometheus + Jaeger -
grafana/provisioning/dashboards/transform-platform.jsonβ JVM + HTTP + business metrics dashboard
Deliverable: docker compose up starts the full stack. App logs are JSON with trace IDs. Traces appear in Jaeger. Business metrics appear in Grafana. All of this is in place before Phase 1 domain code is written.
Phase 1 β Core Domain: Profile, Window, Actionβ
Goal: The aggregate root that drives batch workflows β the platform's unique value.
Phase 1a β Domain Entitiesβ
-
ProfileEntityβ id, name, clientId, status, version, window config (JSONB), tags -
ActionEntityβ owned by Profile, condition, type, executionOrder, config (JSONB), enabled -
WindowInstanceEntityβ runtime state machine (PENDING/OPEN/CLOSING/CLOSED/ERROR), eventCount, openedAt, closedAt -
WindowEventEntityβ individual events collected per window instance, checksum for dedup -
ActionResultEntityβ outcome of each action execution (status, recordsProcessed, error) -
ProfileRevisionEntityβ full JSON snapshot on every PUT (version history / rollback) - Flyway migrations V4βV8 for all new tables
Phase 1b β Profile + Action REST APIβ
-
ProfileControllerβ full CRUD + enable / disable / validate / rollback / history / trigger -
ActionControllerβ sub-resource of Profile: add / update / delete / enable / disable -
WindowControllerβ list instances / get / force-close / reprocess / list events -
ProfileService,ActionService,WindowServicebacked by JPA repositories
Phase 1c β Window Schedulerβ
-
WindowSchedulingServiceβ registers/removes QuartzCronTriggers on enable/disable/update -
WindowOpenJobβ fires onstartCronExpression, createsWindowInstance(PENDING β OPEN), firesON_OPENchain -
WindowCloseJobβ fires onendCronExpression, transitions OPEN β CLOSING β CLOSED, firesON_CLOSING/ON_EMPTY_CLOSE -
WindowRecurringJobβ fires everyrecurringIntervalwhile OPEN, runsRECURRING_WHILE_OPENchain -
WindowStateServiceβ state machine (open,startClosing,close,markError) -
DeduplicationServiceβ SHA-256 checksum check againstWindowEventEntity - Quartz JDBC persistence β store jobs in PostgreSQL (migration V9 for Quartz tables)
Phase 1d β Action Execution Engineβ
-
ActionExecutorinterface βsupports(type): Boolean+execute(action, window): ActionResult -
ActionChainβ ordered execution byexecutionOrder, respectscontinueOnFailure, triggersON_ERRORon failure -
FileToEventsActionExecutorβ wraps existingTransformationPipeline -
EventsToFileActionExecutorβ stub (full wiring in Phase 3) -
NotifyActionExecutorβ simple webhook / log notification (full channels in Phase 2)
Deliverable: A Profile can be created via API, enabled, and windows open/close on schedule with actions executing and state fully persisted. Runnable end-to-end demo.
Phase 2 β Integration Channelsβ
Goal: Pluggable IntegrationChannel system β new module platform-integration.
-
IntegrationChannelinterface +IntegrationChannelRegistry(Spring@Componentauto-discovery) -
ConnectivityTestServiceβ tests any channel without running a pipeline -
SftpChannel(JSCH) β inbound poll + outbound upload + connectivity test -
KafkaChannelβ consumer (collect events into window) + producer (write records to topic) -
DatabaseChannel(Spring JDBC) β poll query + batch insert/upsert -
RestApiChannel(Spring WebClient) β GET poll with pagination + POST webhook fan-out -
S3Channel(AWS SDK v2) β inbound list/download + outbound upload with path templating -
IntegrationControllerinplatform-apiβ full CRUD + connectivity test endpoints
Deliverable: Real SFTP pickup and delivery works. Kafka consumer collects events into a window. S3 archive action works.
Phase 3 β Events β File Pipelineβ
Goal: Complete the outbound direction so EVENTS_TO_FILE actions produce real files.
-
FileGeneratorinterface β mirrorsFileParser:supports(format)+generate(records, config, output) -
FileGeneratorRegistryβ same@Componentauto-discovery asParserRegistry -
CsvFileGeneratorβ generates CSV fromFileRecordusingOutboundConfig.fieldMappings -
FixedWidthFileGeneratorβ generates fixed-width files -
EventsToFilePipelineβEventMapper(event βFileRecord) + correction + validation +FileGenerator - Wire
EventsToFileActionExecutorto useEventsToFilePipeline+IntegrationChannel.upload
Deliverable: A window collects payment events and generates a file delivered via SFTP β all driven by a Profile config with zero code changes.
Phase 4 β Remaining Action Executorsβ
-
ArchiveActionExecutorβ S3Channel archive of window events as JSONL.gz -
ValidateCompletenessActionExecutorβ query DB or external source for expected count -
TransformAndRouteActionExecutorβ parallel fan-out to multiple integration channels -
InvokeExternalActionExecutorβ REST webhook call with retry, timeout, and Handlebars body templating
Phase 5 β Advanced Featuresβ
-
ON_THRESHOLD_REACHEDcondition β configurable event count trigger, re-armable - Profile versioning diff β compare any two revision snapshots side-by-side
-
ON_DUPLICATE_DETECTEDcondition β fires whenDeduplicationServicerejects an event - NACHA / ISO 20022
FileGeneratorimplementations - Built-in FileSpec templates β NACHA-CCD, ISO20022-pain.001, EDI-820 (zero-config onboarding)
Key Design Decisions (Locked)β
| Decision | Choice | Reason |
|---|---|---|
| Action config storage | JSONB column | Flexible, queryable in PostgreSQL, no schema migration per new action type |
| Window event storage | PostgreSQL | Simpler ops; Redis later if high-volume |
| Quartz persistence | JDBC store from day 1 | Enables HA and clustering later without rework |
| Auth | JWT (already declared) | Already in build.gradle.kts, implement in Phase 0 |
platform-pipeline | Remove | Spring Batch is the wrong model; Window/Action replaces it |
platform-common scope | Entities + interfaces + DTOs + exceptions only | No services β keeps the module dependency graph clean |
Fastest Path to a Demoβ
Phase 0 β Phase 1a β Phase 1b β Phase 1c β Phase 1d β Phase 2 (SFTP) β Phase 3
~3h ~2h ~3h ~4h ~3h ~3h ~3h
One concrete scenario that proves the platform end-to-end:
ACME Corp drops a payments CSV on their SFTP server at 08:00 every weekday. A Profile collects the events all day, then at 17:00 generates a NACHA ACH file and delivers it to the Fed's SFTP β driven entirely by JSON config, no code.