Skip to main content
DocsBuild WorkflowModularization and Reuse

Modularization and Reuse

Use sub_workflow, package logic into modules, and refactor with Promote / Demote so workflows no longer have to be copied and pasted one by one.

When you reach the second and third workflows, you will find that a certain piece of logic appears repeatedly - pulling ASA data, sending Telegram messages, and generating Excel reports. If you continue to copy and paste, you will have to change three places to change one place, the test will be repeated, and the version will be out of control. It’s time to do modularization at this time.

Templates Vs Modules Again

Template = a snapshot of the starting document, which evolves independently after cloning; module = a sub-process that can be referenced, and the caller will automatically benefit after the upgrade. The two are not substitutes, but complementary. This page talks about modules.

What Is A "Module"

A "module" is essentially a workflow that declares the top-level fields of WorkflowModuleContract. It additionally exposes:

  • inputs — Parameters that the caller must pass in (with name, type, required, default value, description)
  • outputs — The structured result generated after execution (can be directly referenced by the caller)
  • version — Semantic version number to facilitate caller locking
yaml
name: my-team-slack-notifier
version: "1.0.0"

module:
  enabled: true
  inputs:
    webhook_url:
      type: string
      required: true
      description: Slack incoming webhook URL
    channel:
      type: string
      required: false
      default: "#general"
    message:
      type: string
      required: true
    mentions:
      type: array
      items: { type: string }
      required: false
  outputs:
    message_ts:
      type: string
      description: Slack 返回的 message timestamp,可用于后续更新消息
    status:
      type: string
      enum: [sent, failed]

# 模块自己的步骤(内部细节,对调用方黑盒)
steps:
  - id: format_text
    type: code
    language: node
    code: |
      const msg = process.env.WF_VAR_MESSAGE;
      const mentions = JSON.parse(process.env.WF_VAR_MENTIONS || '[]');
      return { text: mentions.map(u => `<@${u}>`).join(' ') + ' ' + msg };

  - id: post
    type: code
    depends_on: [format_text]
    credentials: [slack_oauth]
    code: |
      //  Slack API,返回 message_ts / status 

After having the contract, the sub_workflow step of the caller (parent workflow) will:

  • Static verification when saving - required inputs will not be saved if one is missing;
  • Verification at runtime - if the type does not match/the enumeration is not in the list, an error will be reported directly;
  • Auto-completion in the editor - when you write inputs: you can scroll down to see which fields the module exposes.

How the Parent Workflow Calls the Module

yaml
steps:
  - id: build_report
    type: code
    # ...

  - id: notify
    type: sub_workflow
    module: my-team-slack-notifier    # 引用我们上面定义的模块
    version: "^1.0.0"                  # 锁主版本
    inputs:
      webhook_url: "{{credentials.slack_webhook}}"
      channel: "#ops-alerts"
      message: "日报已生成:{{steps.build_report.data.summary}}"
      mentions: ["U1234", "U5678"]

  - id: record
    type: code
    depends_on: [notify]
    code: |
      const ts = process.env.STEP_OUTPUT_NOTIFY_MESSAGE_TS;
      // 把 ts 存到你自己的数据库,方便后续更新那条消息 ...

Key points:

  • module: dingyue-module-slack-deliver It is the module id, and the platform will resolve it based on name + version.
  • inputs: Corresponds one-to-one with the inputs in the module contract.
  • After the call is completed, you can pass {{steps.deliver.outputs.message_id}} Read module output.

Turn Existing Workflows Into Modules: Promote to Module

Most of the time you won't write a module from scratch, but rather pull it from an existing workflow. Process:

  1. Open a workflow where you think "this logic is worth reusing".
  2. Use mouse frame selection or Shift+click to select several consecutive steps in the canvas.
  3. Right-click menu → "Promote to module", or the button with the same name on the top toolbar.
  4. In the pop-up window:
    • Give a meaningful module name (globally unique, it is recommended to prefix it with your team name, for example: my-team-slack-notifier);
    • The platform will automatically infer the first draft of inputs / outputs based on the selected fragment, which you can edit;
    • After confirmation, click "Create Module".
  5. These steps in the original workflow will be replaced with a sub_workflow node - parameters and connections are automatically aligned.
Promote’s Mental Model

Like the "extract function" refactoring in the IDE. You select a piece of code, and the IDE will help you extract a function with parameters and return values, and replace the original position with the call. Braidrun does exactly the same thing - but for workflow steps.

Module Upgrade and Version Compatibility

Each time you save a module a new version is created. The parent workflow can be selected in the sub_workflow step:

  • version: latest — Automatically use the latest version (suitable for modules you maintain yourself)
  • version: "1.2.0" — Locked to a specific version (suitable for referencing other people’s modules, fear of upgrading and damaging compatibility)
  • version: "^1.2.0" — Allow minor version upgrades, prohibit cross-version major versions

What is a "compatible change"?

  • ✓ Add an optional input (required/default values are not considered broken)
  • ✓ Add an output field
  • ✗ Delete/rename input
  • ✗ Delete/rename output
  • ✗ Change input type

When making changes that break compatibility, the semantic version should be upgraded to the next MAJOR. The platform does not enforce, but is protected when the caller locks ^X.Y.Z.

Loop Detection

Module A cannot indirectly reference itself. The platform performs detection on two levels:

  1. Static detection at release time — When saving a module, DFS scans the dependency graphs of all other modules it references and refuses to save if there are loops.
  2. Runtime dynamic detection — If a cycle resolved by the runtime occurs during execution (for example, it is referenced after a conditional branch), an error will be thrown immediately.

The default maximum recursion depth is 8 levels. This is the depth of "father's son's son", not a single call to fanout.

Variable Isolation

The module runs as a "child WorkflowExecutionContext", completely isolated from the parent context:

  • The parent's variables/steps outputs/agents are not inherited to the child by default;
  • The child's internal step output will not overflow to the parent - the parent can only see the outputs declared in the contract;
  • The only way to pass data across parent and child is inputs / outputs (explicit contract).

This isolation is the cornerstone of modular reliability - "black box" is not a metaphor, it's a literal one.

Demote from Module: Restore the Module to an Inline Step

If you modify it and find that a certain piece of logic is actually used in only one place, and making it into a module increases the cost of understanding, you can reverse Demote:

  1. Select the sub_workflow node in the parent workflow;
  2. Right click → "Demote inline";
  3. The platform expands the module's step back to the parent workflow, and the sub_workflow node disappears.

The original module itself is not deleted - if there are other workflows that reference it, those are still valid.

Where to Manage All Modules

The main navigation "Module Library" on the left is your overview view. Here you can:

  • See a list of all modules (12 included + those created by your team)
  • Reverse query based on "Who is referencing me" - check who will be affected before changing the module
  • View module version history / diff
  • Publish/Archive/Transfer Ownership

When To Make Modules

  • ✓ The same piece of logic appears in more than 2 workflows
  • ✓ A logically stable and clear external contract (typically "sending messages/checking reports/generating Markdown")
  • ✓ A single workflow has exceeded 15 steps, and its readability has decreased.
  • ✗ A piece of logic is only used once and will not be reused in the future - directly inlined
  • ✗ A small utility function of 2–3 lines - code reuse using code steps is more natural than modules

Next

  • Built-In Module Library — 12 platforms come with built-in modules. Use them first to familiarize yourself with the sub_workflow calling routine.
  • 8 Step Types — Full field reference for sub_workflow in
  • Best Practices — "When and how to dismantle"