Categories
ETL (13)
- docs/features/ETL/brink-pos-first-integration.md
- docs/features/ETL/etl-source-adapter-framework.md
- docs/features/ETL/fleet/fleet-customer-upload-contract.md
- docs/features/ETL/fleet/fleet-pre-ingestion-processing-architecture.md
- docs/features/ETL/how-to-add-etl-source-adapter.md
- docs/features/ETL/index.md
- docs/features/ETL/kds-cdc/branch-direction-and-flow.md
- docs/features/ETL/kds-cdc/debezium-payload-compatibility-notes.md
- docs/features/ETL/kds-cdc/implementation-breakdown.md
- docs/features/ETL/kds-cdc/index.md
- docs/features/ETL/kds-cdc/kds-cdc-scaling-and-incremental-curation.md
- docs/features/ETL/kds-cdc/performance-and-scaling-notes.md
- docs/features/ETL/tenant-warehouse-schema-evolution.md
How To Add An ETL Source Adapter
Purpose
This document is an onboarding guide for adding a new ETL source to the current framework.
It is written for a developer who needs a practical implementation path, not just architecture context.
Before You Start
Read these first:
docs/features/ETL/etl-source-adapter-framework.mddocs/ETL.md- the existing Brink implementation under
backend/Kds.Ai.Integrations/BrinkPos/
The Brink adapter is the reference implementation for the current framework shape.
Design Rules
When adding a new source:
- keep framework mechanics in
backend/Kds.Ai.Integrations/Etl/, - keep source-specific code inside the source adapter package,
- reuse shared stage support and curated executor instead of cloning Brink patterns,
- do not redesign the runtime unless the new source exposes a real shared need.
What The Framework Expects
At minimum a new source should provide:
- one
IEtlSourceAdapter, - one or more
IEtlStageimplementations, - source-specific extraction logic,
- source-specific catalogs through shared catalog contracts,
- DI registration.
Implementation Checklist
Step 1. Define source identity
Create an EtlSourceDescriptor for the source.
It should define:
- stable
SourceTypestring, - matching
IngestionSourceType.
Example shape:
public static readonly EtlSourceDescriptor Source = new(
"my_source",
IngestionSourceType.MySource);Guidelines:
SourceTypeshould be stable and storage-safe,- use one canonical string everywhere,
- avoid aliases unless there is a migration reason.
Step 2. Decide stage list
Choose which stages the source actually needs.
Today the framework supports ordered stage registration. The common baseline is:
extract_rawnormalize_loadload_to_stagingstaging_to_curated
If the source does not need one of these stages, decide that explicitly and document why.
Do not add a custom runner branch for one source.
Step 3. Implement stage wrappers
Create thin stage wrappers implementing IEtlStage.
Recommended pattern:
- wrapper reads upstream result from
EtlStageContext, - wrapper maps framework input to source-specific job input,
- wrapper delegates to source-specific service,
- wrapper returns
EtlStageResult.
Example shape:
internal sealed class MySourceNormalizeStage(IMySourceNormalizeJobs jobs) : IEtlStage
{
public string StageName => EtlStageNames.NormalizeLoad;
public async Task<EtlStageResult> ExecuteAsync(EtlStageContext context, CancellationToken ct = default)
{
var extract = context.GetRequiredOutput<MyExtractResult>(EtlStageNames.ExtractRaw);
var result = await jobs.ExecuteAsync(new MyNormalizeInput(...), ct);
return new EtlStageResult(StageName, result.BusinessDate, result);
}
}Step 4. Keep source logic in source services
Do not move source-specific behavior into the framework.
Keep inside the adapter package:
- credentials,
- API clients,
- parsing,
- business date logic,
- source normalization logic,
- SQL target content.
The framework should call the source code, not absorb it.
Step 5. Reuse shared stage support
If a stage follows the same downstream pattern as Brink normalize_load, load_to_staging, or staging_to_curated, use IStageExecutionSupport.
Use it for:
- checkpoint advancement on success,
- failed package writing,
- batch failure integration.
This avoids per-source drift in operational behavior.
Step 6. Reuse shared curated executor where possible
If the source has a staging_to_curated phase with ordered SQL target execution, use ICuratedTargetExecutor.
The source should still own:
- target list,
- SQL text,
- dependency rules,
- target-specific diagnostics mapping.
The framework owns:
- transaction,
- parameter binding,
- commit / rollback unit.
Step 7. Implement catalogs
Expose source metadata through the shared contracts:
IExtractOperationCatalogINormalizationContractCatalogIStagingTableCatalogICuratedTargetCatalog
The content stays source-specific.
Recommended validations:
- every staging table maps to a known normalization contract,
- every curated dependency references a known staging table,
- every active extract operation has downstream ownership.
Step 8. Implement the adapter
Create one adapter class implementing IEtlSourceAdapter.
It should:
- expose
Descriptor, - return the active ordered stage list,
- keep stage composition source-local.
Example shape:
internal sealed class MySourceAdapter(
MyExtractStage extract,
MyNormalizeStage normalize,
MyLoadStage load,
MyCuratedStage curated) : IEtlSourceAdapter
{
public EtlSourceDescriptor Descriptor { get; } = new("my_source", IngestionSourceType.MySource);
public IReadOnlyList<IEtlStage> GetStages() => [extract, normalize, load, curated];
}Step 9. Register in DI
Add registrations in:
backend/Kds.Ai.Integrations/DependencyInjectionExtensions.cs
At minimum register:
- source services,
- stage wrappers,
IEtlSourceAdapterimplementation.
If the source uses shared helpers, it should consume existing registrations rather than creating new copies.
Step 10. Wire an entry point
If the source needs scheduled or manual execution, create an application job similar to BrinkPosEtlJobs.
That entry point should:
- gather runtime input,
- select the right adapter,
- call
IEtlPipelineRunner.RunAsync(...).
Do not hardcode a new per-stage call chain in the job.
Recommended Folder Shape
Suggested source structure:
backend/Kds.Ai.Integrations/MySource/
MySourceAdapter.cs
MySourceExtractStage.cs
MySourceNormalizeStage.cs
MySourceLoadToStagingStage.cs
MySourceStagingToCuratedStage.cs
MySourceExtractJobs.cs
MySourceNormalizeLoadJobs.cs
MySourceLoadToStagingJobs.cs
MySourceStagingToCuratedJobs.cs
MySourceEndpointCatalog.cs
MySourceNormalizationCatalog.cs
MySourceStagingTableCatalog.cs
MySourceCuratedTargetCatalog.cs
Models/Adjust the exact set only if the source genuinely needs less.
Data Flow Template
flowchart TD
A[Entry Job] --> B[IEtlPipelineRunner]
B --> C[MySourceAdapter]
C --> D[extract_raw]
D --> E[normalize_load]
E --> F[load_to_staging]
F --> G[staging_to_curated]Delivery Checklist
Before considering a new source complete, verify:
- adapter returns the correct ordered active stage list,
- pipeline runs through
IEtlPipelineRunner, - stage outputs are passed through
EtlStageContext, - checkpoint advances only on success,
- failed packages are written on failure,
- curated SQL runs through shared transaction handling if applicable,
- catalogs are internally consistent,
- DI resolves the adapter successfully,
- scheduler or manual entrypoint resolves the adapter through framework seams.
Testing Checklist
Add tests for:
- stage ordering,
- upstream result usage,
- checkpoint advancement,
- failed package behavior,
- curated executor success and rollback if used,
- catalog consistency,
- adapter stage list,
- entrypoint wiring.
Use the Brink tests as the template:
backend/tests/Kds.Ai.Integrations.Tests/Etl/EtlPipelineRunnerTests.csbackend/tests/Kds.Ai.Integrations.Tests/Etl/StageExecutionSupportTests.csbackend/tests/Kds.Ai.Integrations.Tests/Etl/CuratedTargetExecutorTests.csbackend/tests/Kds.Ai.Integrations.Tests/BrinkPos/BrinkPosEtlCatalogTests.cs
Common Mistakes To Avoid
- adding a new hardcoded pipeline job chain instead of using
IEtlPipelineRunner, - moving source-specific parsing or SQL into framework folders,
- duplicating failure/checkpoint logic inside the source job,
- bypassing
EtlStageContextand coupling stages directly, - creating source-specific transaction execution code when shared curated executor already fits,
- leaving catalog ownership unclear.
Minimal Success Definition
A new source is integrated correctly when:
- the app can resolve the adapter from DI,
- the entrypoint calls the pipeline runner,
- stages execute in source-declared order,
- shared mechanics are reused,
- source-specific logic stays within the adapter boundary.