An end-to-end Azure Data Factory (ADF) project demonstrating real-world data engineering patterns β REST API ingestion, on-premises file ingestion, incremental loading, watermark management, orchestration, alerting, audit logging, and REST API pagination.
I've documented the incremental loading concepts and implementation behind this project in a 4-part Medium series. Click a button below to jump straight to the article.
π‘ For the incremental pipelines in this repository, start with Part 1 and follow the series sequentially.
This project contains 7 Azure Data Factory pipelines, each covering a common real-world data engineering scenario.
| # | Pipeline | Key Concept |
|---|---|---|
| 1 | pl_API_Ingestion |
REST API β ADLS Gen2 |
| 2 | pl_OnPremFiles_To_AzureBlobBronze |
On-Prem Files β ADLS Gen2 |
| 3 | pl_SQLtoAzure_Incremental_Json |
Incremental Load + JSON Watermark |
| 4 | pl_dailydata_load_with_alerts |
Orchestration + Logic App Alerts |
| 5 | pl_onPremSQl_To_AzureSQL_Incremental_WatermarkTable |
Multi-Table Incremental Load |
| 6 | pl_onPremSQl_To_AzureSQL_Incremental_WatermarkTable_ScriptActivity |
Incremental Load + Audit Logging |
| 7 | pl_paginationExample |
REST API Pagination |
pl_API_Ingestion
Pulls DimAirport.json from GitHub Raw content into the ADLS Gen2 Bronze layer.
GitHub Raw β Copy API Data β ADLS Gen2 Bronze
- Source:
ds_APISourceβls_githubAPI_sourceβ HttpServer / Anonymous βDimAirport.json - Sink:
ds_github_sinkβls_bronzeβ ADLS Gen2 βbronze/github/DimAirport.json - Key Settings:
JsonSourcewithHttpReadSettings, HTTP GET,JsonSinkwithAzureBlobFSWriteSettings,retry = 2,retryIntervalInSeconds = 30,enableStaging = false
pl_OnPremFiles_To_AzureBlobBronze
Migrates 3 CSV files in parallel from an on-premises Windows file server to ADLS Gen2 Bronze: DimAirline.csv, DimFlight.csv, DimPassenger.csv.
On-Prem Windows File Server β ForEach (Parallel) β Copy Activity β ADLS Gen2 Bronze
- Pipeline Parameter:
file_nameβ e.g.[{"fileName": "DimAirline.csv"}, {"fileName": "DimFlight.csv"}, {"fileName": "DimPassenger.csv"}] - Key Expressions:
@pipeline().parameters.file_name,@item().fileName,@dataset().p_fileName_source,@dataset().p_fileName_sink - Source:
ds_OnPremSourceβls_onPrem_Filesβ FileServer / IROnPremise βE:\OnPremiseFiles\{fileName} - Sink:
ds_onPremSinkβls_bronzeβ ADLS Gen2 βbronze/OnPremDataFiles/{fileName}
pl_SQLtoAzure_Incremental_Json
Performs an incremental load from Azure SQL FactBookings to ADLS Gen2 in Parquet format, using a JSON file as the watermark store.
LastLoad Lookup ββ
βββ Copy SQL Data β Parquet
LatestLoad Lookup β
β
Update Watermark
- Last Load: reads
lastload.jsonfrom Azure Blob to get the last processedbooking_date - Latest Load:
SELECT MAX(booking_date) AS LatestLoad FROM dbo.FactBookings; - Incremental Query:
SELECT * FROM dbo.FactBookings WHERE booking_date > '@{activity('LastLoad').output.firstRow.lastload}' AND booking_date <= '@{activity('LatestLoad').output.firstRow.LatestLoad}';
- Sink: Azure SQL β Copy Activity β ADLS Gen2 β
Bronze/sql/β Parquet + Snappy compression - Watermark Update: the latest watermark is written back to
lastload.json
π Detailed explanation in Part 1 of the Medium series above.
pl_dailydata_load_with_alerts
Orchestrator pipeline that calls the incremental pipeline and reports execution status to an Azure Logic App.
DailyDataLoad β Execute Pipeline β pl_SQLtoAzure_Incremental_Json β Call Logic App
- Execute Pipeline: calls
pl_SQLtoAzure_Incremental_JsonwithwaitOnCompletion = true - Logic App Trigger: fires on both β Success and β Failure
- Payload:
{ "pipeline_name": "@{pipeline().Pipeline}", "run_id": "@{pipeline().RunId}", "status": "@{activity('DailyDataLoad').Status}", "error": "@{if(equals(activity('DailyDataLoad').Status,'Failed'), string(activity('DailyDataLoad').error), 'No Error')}" }
pl_onPremSQl_To_AzureSQL_Incremental_WatermarkTable
Multi-table incremental loading from On-Premises SQL Server to Azure SQL using a centralized watermark table, for dbo.iplteams and dbo.salesitems, both keyed on a last_updated watermark column.
ForEach Table
β
Old Watermark ββ
ββ If Condition ββ TRUE β Copy β Update Watermark
New Watermark ββ β FALSE β Skip
- Old Watermark:
SELECT WatermarkValue FROM watermarktable WHERE TableName = '@{item().TableName}'; - New Watermark:
SELECT MAX(last_updated) AS NewWatermarkValue FROM @{item().TableName}; - If Condition:
@greater(activity('New_LookUp_Col').output.firstRow.NewWatermarkValue, activity('Old_LookUp_Col').output.firstRow.WatermarkValue) - Dynamic SQL built from table schema, table name, watermark column, old and new watermark values
- Sink:
AzureSqlSink,writeBehavior = upsert,useTempDB = true,keys = [id] - Watermark Update: stored procedure
[dbo].[usp_write_watermark]with parameterslast_updated,tableName
π Detailed explanation in the Medium series above.
pl_onPremSQl_To_AzureSQL_Incremental_WatermarkTable_ScriptActivity
An enhanced version of Pipeline 5 adding execution timing, a Script Activity for watermark updates, success/failure audit logging, rows-copied tracking, and error tracking.
Set Start Time β Old Watermark Lookup β New Watermark Lookup β If Condition
β
Copy β Success/Failed β Audit
- Set Start Time:
PipelineStartTime = @utcNow() - Script Activity β Update Watermark:
UPDATE dbo.watermarktable SET WatermarkValue = @last_updated WHERE TableName = @tableName;
- Success/Failure Audit writes to
dbo.PipelineAudit:PipelineName,TableName,SourceName,TargetName,WatermarkOld,WatermarkNew,RowsCopied,Status,ErrorMessage,StartTime,EndTime - Key Expressions:
@pipeline().Pipeline,@item().TableName,@activity('Copy_FromOnPremSQL_ToAzureSQL').output.rowsCopied,@activity('Copy_FromOnPremSQL_ToAzureSQL').error.message,@variables('PipelineStartTime'),@utcNow() - Evolution from Pipeline 5: the old
SP_Update_WaterMarkactivity is now inactive β watermark updates are handled by the Script Activity instead
π Detailed explanation in the Medium series above.
pl_paginationExample
Ingests PokΓ©mon data from the PokΓ©API across multiple pages using ADF's native RANGE pagination.
PokΓ©API β Web Activity β Get Total Count β Copy Activity β RANGE Pagination β ADLS Gen2 Bronze
- Initial Request:
https://pokeapi.co/api/v2/pokemon?limit=20&offset=0 - Pagination Rule:
QueryParameters.offset = RANGE:0:@{activity('Get API Data').output.count}:20β generatesoffset=0, 20, 40, ... 1300 - Sink:
ds_API_Target_JSONβ ADLS Gen2 βbronze/PaginationExample/
REST API ingestion Β· ADLS Gen2 Β· On-Premises File Server Β· Self-hosted Integration Runtime Β· ForEach activity Β· Parallel processing Β· Pipeline parameters Β· Dataset parameters Β· Dynamic expressions Β· Incremental loading Β· JSON watermark Β· Watermark table Β· Azure SQL Upsert Β· Execute Pipeline Β· Web Activity Β· Logic App integration Β· Script Activity Β· Audit logging Β· Error handling Β· REST API pagination Β· RANGE pagination
I built multiple Azure Data Factory pipelines covering real-world data engineering scenarios β REST API ingestion, parallel on-premises file migration, watermark-based incremental loading, multi-table processing using ForEach, Azure SQL upserts, Logic App alerts, Script Activity-based watermark management, audit logging, and REST API pagination.
github.com/Prabhas92/ADFProject
Chethan Prabhas
Azure Data Factory β’ SQL β’ Azure β’ Python β’ Data Engineering