Skip to main content

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.md at the repo root is the authoritative copy β€” this page mirrors it for visibility.


Ground Truth: Where We Stand​

Code that actually exists and works​

ModuleStatusWhat's built
platform-coreβœ… DoneCSV / Fixed-Width / XML parsers, CorrectionEngine, ValidationEngine, TransformationPipeline, KafkaRecordWriter, ParserRegistry, unit tests
platform-api⚠️ PartialSpecController, TransformController β€” in-memory HashMaps, no DB persistence, paths are /api/v1/...
platform-common❌ Emptybuild.gradle.kts only
platform-scheduler❌ EmptyQuartz dependency declared, no code
platform-pipelineπŸ—‘οΈ To removeSpring 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, CredentialEntity as JPA entities with JSONB columns for nested config
  • Add Flyway to platform-api β€” migrations V1__create_filespecs.sql, V2__create_integrations.sql, V3__create_credentials.sql
  • Wire application.yml datasource config and replace HashMap in SpecService with FileSpecRepository
  • 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-pipeline module (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:

SignalToolPort
TracesJaeger (via OTel Collector):16686
MetricsPrometheus β†’ Grafana:9090 / :3000
LogsElasticsearch β†’ Kibana:9200 / :5601

Code changes only (no new modules):

  • platform-api/build.gradle.kts β€” add micrometer-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 with traceId, spanId, correlationId in every line
  • ObservabilityConfig.kt β€” Micrometer common tags (service name, env)
  • CorrelationIdFilter.kt β€” inject + propagate X-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, WindowService backed by JPA repositories

Phase 1c β€” Window Scheduler​

  • WindowSchedulingService β€” registers/removes Quartz CronTriggers on enable/disable/update
  • WindowOpenJob β€” fires on startCronExpression, creates WindowInstance (PENDING β†’ OPEN), fires ON_OPEN chain
  • WindowCloseJob β€” fires on endCronExpression, transitions OPEN β†’ CLOSING β†’ CLOSED, fires ON_CLOSING / ON_EMPTY_CLOSE
  • WindowRecurringJob β€” fires every recurringInterval while OPEN, runs RECURRING_WHILE_OPEN chain
  • WindowStateService β€” state machine (open, startClosing, close, markError)
  • DeduplicationService β€” SHA-256 checksum check against WindowEventEntity
  • Quartz JDBC persistence β€” store jobs in PostgreSQL (migration V9 for Quartz tables)

Phase 1d β€” Action Execution Engine​

  • ActionExecutor interface β€” supports(type): Boolean + execute(action, window): ActionResult
  • ActionChain β€” ordered execution by executionOrder, respects continueOnFailure, triggers ON_ERROR on failure
  • FileToEventsActionExecutor β€” wraps existing TransformationPipeline
  • 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.

  • IntegrationChannel interface + IntegrationChannelRegistry (Spring @Component auto-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
  • IntegrationController in platform-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.

  • FileGenerator interface β€” mirrors FileParser: supports(format) + generate(records, config, output)
  • FileGeneratorRegistry β€” same @Component auto-discovery as ParserRegistry
  • CsvFileGenerator β€” generates CSV from FileRecord using OutboundConfig.fieldMappings
  • FixedWidthFileGenerator β€” generates fixed-width files
  • EventsToFilePipeline β€” EventMapper (event β†’ FileRecord) + correction + validation + FileGenerator
  • Wire EventsToFileActionExecutor to use EventsToFilePipeline + 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_REACHED condition β€” configurable event count trigger, re-armable
  • Profile versioning diff β€” compare any two revision snapshots side-by-side
  • ON_DUPLICATE_DETECTED condition β€” fires when DeduplicationService rejects an event
  • NACHA / ISO 20022 FileGenerator implementations
  • Built-in FileSpec templates β€” NACHA-CCD, ISO20022-pain.001, EDI-820 (zero-config onboarding)

Key Design Decisions (Locked)​

DecisionChoiceReason
Action config storageJSONB columnFlexible, queryable in PostgreSQL, no schema migration per new action type
Window event storagePostgreSQLSimpler ops; Redis later if high-volume
Quartz persistenceJDBC store from day 1Enables HA and clustering later without rework
AuthJWT (already declared)Already in build.gradle.kts, implement in Phase 0
platform-pipelineRemoveSpring Batch is the wrong model; Window/Action replaces it
platform-common scopeEntities + interfaces + DTOs + exceptions onlyNo 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.