Build the Next Layer, Not the Whole Thing
There is a version of this story in every client engagement I have been on. The first pipeline works, so someone immediately wants to add retry logic, parameterization, alerting, data quality checks, and a metadata-driven configuration framework — all at once, in the same sprint.
The result is always the same. The thing that worked stops working, nobody is sure why, and the scope of the problem is now twice as large as when you started.
Here is the discipline that prevents it: build the next layer, not the whole thing.
What You Already Have Is Enough to Start
Pick up from the last post. You have a coordinator procedure, RunOrderBatch, calling three focused units: extract, validate, transform. It runs. You have tested it. It does what it says.
That is your baseline. It is worth more than any half-finished abstraction you could add to it today.
Adding parameterization and error handling at the same time means you will not know which change broke something when something breaks. And something always breaks.
Add One Layer: Parameterization
Make your coordinator data-driven — one bounded change:
CREATE TABLE Orchestration.BatchConfig (
BatchId INT IDENTITY PRIMARY KEY,
BatchDate DATE NOT NULL,
PipelineName VARCHAR(100) NOT NULL,
Status VARCHAR(20) NOT NULL DEFAULT 'pending',
CreatedAt DATETIME NOT NULL DEFAULT GETUTCDATE(),
StartedAt DATETIME NULL,
CompletedAt DATETIME NULL
);Now your top-level coordinator reads from this table instead of taking a hard-coded date:
CREATE PROCEDURE dbo.RunNextPendingBatch
AS
BEGIN
SET NOCOUNT ON;
DECLARE @BatchId INT, @BatchDate DATE;
SELECT TOP 1 @BatchId = BatchId, @BatchDate = BatchDate
FROM Orchestration.BatchConfig
WHERE Status = 'pending' ORDER BY BatchDate ASC;
IF @BatchId IS NULL BEGIN PRINT 'No pending batches.'; RETURN; END;
UPDATE Orchestration.BatchConfig
SET Status = 'running', StartedAt = GETUTCDATE()
WHERE BatchId = @BatchId;
EXEC dbo.RunOrderBatch @BatchDate = @BatchDate;
UPDATE Orchestration.BatchConfig
SET Status = 'complete', CompletedAt = GETUTCDATE()
WHERE BatchId = @BatchId;
END;That is one new layer: a coordinator above your coordinator. RunNextPendingBatch knows about batch scheduling. RunOrderBatch knows about orders. Separation intact.
Simple, Right?
Before you add error handling, sit with what you just got for free: you can queue multiple batch dates by inserting rows into BatchConfig, inspect run status, and see exactly when each batch started and finished. None of that was in the original pipeline. It emerged from adding one clean layer on top of what already worked.
Each layer gives you more than the capability itself — observability, control surface, and a place to attach the next layer.
Add Error Handling — As Its Own Layer
With parameterization stable, wrap execution in TRY/CATCH. The constraint: RunOrderBatch and the three units below it do not change.
BEGIN TRY
EXEC dbo.RunOrderBatch @BatchDate = @BatchDate;
UPDATE Orchestration.BatchConfig
SET Status = 'complete', CompletedAt = GETUTCDATE()
WHERE BatchId = @BatchId;
END TRY
BEGIN CATCH
UPDATE Orchestration.BatchConfig
SET Status = 'failed', CompletedAt = GETUTCDATE()
WHERE BatchId = @BatchId;
RAISERROR(ERROR_MESSAGE(), 16, 1);
END CATCH;You added capability above the existing system without touching the existing system. That is the discipline.
The Gotcha: Layers That Leak
The failure mode is smuggling coupling back in. If RunNextPendingBatch sets a flag that ExtractRawOrders reads to decide whether to skip records, the extract layer now knows about scheduling logic it has no business knowing. You can no longer test it independently.
Keep information flowing one direction: down. Coordinators pass parameters to units. Units do not read state set by coordinators. If a unit needs to make a routing decision, that is the coordinator's job — not the unit's.
What Is Next
The next post flips direction entirely. Instead of starting from what works and building up, we start from what you want the system to do and decompose it down — epics to stories to tasks, applied to a data pipeline project. Same principle, opposite entry point. As always, I am here to help.