You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Workflow DSL (#310) covers job-to-job relationships only. The data flow inside a single Spark job needs its own abstraction. This task implements the Step model for Spark — Source / Flow / Merge / Split / Sink composition, both as a Scala DSL and as inline YAML via StepsRunnerJob. Other job kinds (e.g., bash) are out of scope.
A Spark job can be expressed in Scala via Source ~> Flow ~> Sink with type-state safety (a chain without a Sink fails to compile).
A Spark job can be expressed in YAML via kind: spark, mainClass: StepsRunnerJob, and an inline steps: list.
Memoization is verifiable: a shared upstream feeding multiple sinks runs only once within a run.
fanOut runs each branch against a cached upstream and unpersists after.
A Split step produces M named outputs from one input, addressable via as: {port: label} (YAML) or forked("port") (Scala); the split body runs once per execution.
All listed plan-time validations reject invalid graphs before execution.
Notes
Step signatures
Kind
Signature
In
Out
Source
read(): DataFrame
0
1
Flow
apply(in: DataFrame): DataFrame
1
1
Merge
apply(inputs: Seq[(label, DataFrame)]): DataFrame
N
1
Split
split(in: DataFrame): Map[String, DataFrame]
1
M
Sink
write(df: DataFrame): Unit
1
0
A Merge receives labeled inputs, so consumers like SqlMerge use each label as the temp view name. The default label for a single-input chain is "_0". A Split declares output ports (string keys) addressed by downstream via as: {port: label} or forked("port").
Scala DSL
classJoinJobextendsJob[JoinCfg] {
overridedefplan(cfg: JoinCfg):Plan.Closed= {
valusers=FileSource("users.parquet").as("u")
valevents=FileSource("events.parquet").as("e")
(users + events) ~>SqlMerge("SELECT u.name, e.kind FROM u JOIN e ON u.id = e.user_id") ~>ShowSink()
}
}
Operator
Meaning
~>
pipe
+
combine for multi-input Merge
.as("name")
label this output (also the SQL view name)
fanOut(...)
broadcast: a shared upstream feeds multiple Sink branches with the same data
forked("port")
select an output port of a Split
YAML inline — StepsRunnerJob
jobs:
pi:
kind: sparkartifact: "com.kakao.actionbase:pipeline:0.x"mainClass: StepsRunnerJobargs:
steps:
- step: SampleSourceargs: { n: 1000000, columns: [x, y] }
- step: SqlMergeargs:
query: "SELECT 4.0*SUM(CASE WHEN x*x+y*y<=1 THEN 1 ELSE 0 END)/COUNT(*) AS pi FROM _0"
- step: ShowSink
Field
Meaning
step
class name (short / sub-package / FQN)
args
bound to case class fields
as
output label. String for Source/Flow/Merge; map {port: label} for Split
Background
Workflow DSL (#310) covers job-to-job relationships only. The data flow inside a single Spark job needs its own abstraction. This task implements the Step model for Spark — Source / Flow / Merge / Split / Sink composition, both as a Scala DSL and as inline YAML via
StepsRunnerJob. Other job kinds (e.g., bash) are out of scope.Task
Stepsealed trait:Source(0→1) /Flow(1→1) /Merge(N→1) /Split(1→M) /Sink(1→0)PlanDSL:~>,+,.as,fanOut, port selector for SplitfanOutcache, Split port memo, plan-time validationStepsRunnerJobfor inline YAML chainsFileSource,SampleSource,SqlMerge,CacheFlow,FileSink,ShowSinkDone When
Source ~> Flow ~> Sinkwith type-state safety (a chain without a Sink fails to compile).kind: spark,mainClass: StepsRunnerJob, and an inlinesteps:list.fanOutruns each branch against a cached upstream and unpersists after.Splitstep produces M named outputs from one input, addressable viaas: {port: label}(YAML) orforked("port")(Scala); the split body runs once per execution.Notes
Step signatures
read(): DataFrameapply(in: DataFrame): DataFrameapply(inputs: Seq[(label, DataFrame)]): DataFramesplit(in: DataFrame): Map[String, DataFrame]write(df: DataFrame): UnitA Merge receives labeled inputs, so consumers like
SqlMergeuse each label as the temp view name. The default label for a single-input chain is"_0". A Split declares output ports (string keys) addressed by downstream viaas: {port: label}orforked("port").Scala DSL
~>+.as("name")fanOut(...)forked("port")YAML inline —
StepsRunnerJobstepargsas{port: label}for Splitinputs"_0")Related: #310 (workflow DSL), #311 (initial PR — closed in favor of split).