Skip to content
Back to skills

Functional Designing Workflow Pipelines

ASecurity

Use when implementing a use case, command handler, or business process, or when logic for one operation is scattered across services and layers.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 21, 2026
ai-agentsgoshelldatabasedocumentation

Works with

  • cli

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 21, 2026

npx -y skills add yutna/functional-design-skills --skill functional-designing-workflow-pipelines --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Functional Designing Workflow Pipelines?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Functional Designing Workflow Pipelines
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yutna-functional-designing-workflow-pipelines/badge)](https://www.skillsdirectory.com/skills/yutna-functional-designing-workflow-pipelines)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: functional-designing-workflow-pipelines
description: Use when implementing a use case, command handler, or business process, or when logic for one operation is scattered across services and layers.
license: MIT
metadata:
  pack: functional-design-skills
  version: 3.3.0
---

# Designing Workflow Pipelines

**In this pack:** [how much design a task warrants](../functional-design/references/calibration.md),
[the notation](../functional-design/references/notation.md), and
[syntax per language](../functional-design/SKILL.md#language-packs).

## Overview

A workflow decides one business process: a command comes in, with the
values a handler read for it, and events come out. Modelled as a
pipeline, it is a sequence of small total functions, each with its own
input and output types, composed into one function whose signature
states the whole contract.

This shape is what makes a domain model executable. The types along the
pipeline record which checks have already happened, so no step re-checks;
the pipeline is pure from end to end, so the only places an effect can
be are before it and after it; and the whole use case can be read in one
place instead of being assembled in a reader's head from four services.
A workflow whose reads cannot all come first is two such pipelines, with
the read between them.

## When to use

- Implementing a use case, endpoint handler, job, or message consumer
- Business logic for one operation lives in three files
- A service method is long and mixes validation, rules, and I/O
- Deciding what the steps of an operation are
- Turning an event-storming result into code

Not for: designing the data itself, which is
[functional-modeling-with-algebraic-types](../functional-modeling-with-algebraic-types/SKILL.md).

## Core rules

1. Default. **Start with the end points.** Write the workflow's own signature
   before its steps: the command and the values read for it in, `Result`
   of events out. A decision nothing can refuse returns its events with
   no `Result` around them.
2. Rule. **One type per stage.** The output of a step is a type that did not
   exist before it ran, so later steps cannot receive unvalidated data. A step
   whose output would be the type it took is either a validation that should
   return a narrower type, or not a step: fold it into its neighbour.
3. Rule. **Each step is total.** Every failure the business can name is in
   the return type, never thrown and never a null.
4. Rule. **A workflow performs nothing.** What its steps need from the
   world is read by the handler and passed in as values, and what it
   decides is written by the handler after it returns. Where a read
   cannot come first, the pipeline is cut there into two pure functions
   and the handler reads between them. See
   [functional-separating-pure-core-from-shell](../functional-separating-pure-core-from-shell/SKILL.md).
5. Default. **Steps are named in the domain's words**, and each one is
   meaningful to a domain expert. The sub-steps inside one step are outside
   this: they are private to it, appear in no workflow's signature, and are
   named for what they do.
6. Default. **The pipeline is the documentation.** Reading the composed function
   should tell a reader what the business process is. Where the pipeline is cut
   in two around a read, the handler's chain is what reads as the process, and
   each half reads as its part of it.
7. Rule. **Return events, not actions.** A workflow returns the events it
   decided, each a fact that is true once the decision is saved. An
   event carries what changed, as `BookingConfirmed` carries the
   confirmed booking, and the handler saves the event and the state in
   one transaction. What follows from an event, a message to send or a
   schedule to update, is the work of whichever handler receives it.

## Pattern

Scattered across layers:

```text
-- controller: parses, calls service, maps errors, sends email
-- service: validates, calls two repositories, prices, saves, publishes
-- repository: also validates, because the service sometimes forgets
```

Nobody can state the process without reading three files, and the
validation rule now has two homes.

As a pipeline:

```text
confirmBooking :
  Catalogue -> Instant -> UnvalidatedBooking
    -> Result<List<ConfirmBookingEvent>, ConfirmBookingError>

confirmBooking catalogue now unvalidated =
  validateBooking catalogue unvalidated
    |> mapError Validation
    |> bind (priceBooking catalogue >> mapError Pricing)
    |> map (issueConfirmation now)
    |> map (\booking -> [BookingConfirmed booking])
```

Three steps, each with its own types, and a last line that reports what
was decided:

```text
validateBooking :
  Catalogue -> UnvalidatedBooking
    -> Result<ValidatedBooking, NonEmptyList<ValidationError>>

priceBooking :
  Catalogue -> ValidatedBooking -> Result<PricedBooking, PricingError>

issueConfirmation : Instant -> PricedBooking -> ConfirmedBooking

type ConfirmBookingError =
  | Validation of NonEmptyList<ValidationError>
  | Pricing of PricingError
```

The signature of `confirmBooking` states the whole contract. The steps state
the process. Nothing else in the system needs to know the order of them.

`Catalogue` and `Instant` are values. The catalogue is the treatments
the clinic offers and their prices, read before the workflow runs, and
the instant is the one clock reading taken for this command.

## Anatomy

| Part         | Rule                                          |
| ------------ | --------------------------------------------- |
| Command      | The caller's unchecked input, as a record     |
| Values read  | Parameters, read by the handler beforehand    |
| Stage types  | One per completed step, never reused          |
| Error type   | One choice type for the whole workflow        |
| Output       | The events it decided, usually as a list      |
| Composition  | `bind` for a step that can fail, `map` if not |

More on each in
[pipeline-anatomy.md](references/pipeline-anatomy.md).

## Choosing the steps

Ask the domain expert to describe the process, and take the steps from
their sentences. Where there is no expert to ask,
[working-alone.md](../functional-design/references/working-alone.md)
says what stands in for one. Then check each candidate step against
three tests:

1. **Would an expert name it?** If not, it is an implementation detail,
   and belongs inside another step.
2. **Does it change the type?** A step whose input and output types are
   the same is either a validation that should return a narrower type, or
   not a step.
3. **Could it fail for its own reason?** If two adjacent steps always
   fail together and for the same reason, they are one step.

Typical shapes: validate, decide, price or calculate, acknowledge. Not
every workflow has all of them, and inventing empty ones is how
pipelines become ceremony. Enriching is not on the list because it is a
read the handler makes before the workflow, and recording is the write
it makes after.

## Effects around the pipeline

No step performs an effect, and no step takes a capability. A step that
seems to need I/O, a slot availability check or a price lookup, needs a
value, and the handler reads that value first.

```text
-- shell: the handler reads, calls the workflow, and writes
type ConfirmBookingFailure =
  | Rejected of ConfirmBookingError
  | Unavailable of StoreError

handleConfirmBooking :
  Pool -> Instant -> UnvalidatedBooking
    -> AsyncResult<List<ConfirmBookingEvent>, ConfirmBookingFailure>
handleConfirmBooking pool now unvalidated =
  loadCatalogue pool                                    -- read
    |> mapError Unavailable
    |> bind (\catalogue ->
         confirmBooking catalogue now unvalidated       -- decide
           |> mapError Rejected)
    |> bind (saveEvents pool >> mapError Unavailable)   -- write
```

The handler's failure type has two cases because two different things
can go wrong: the rules refused the booking, or storage could not be
reached. The workflow's own error type knows only the first.
`saveEvents` writes the booking and its events in one transaction and
hands the events back.

Where a read cannot come first, as when a rule picks which catalogue
applies, cut the pipeline at that point and let the handler read between
the two halves. Each half is then a pure pipeline with a signature and
an error type of its own, and the handler lifts both into its failure
type. Rule 1 of
[functional-parameterizing-dependencies](../functional-parameterizing-dependencies/SKILL.md)
says when a read cannot come first, and
[worked-example.md](references/worked-example.md) builds a workflow that
is cut this way.
[effect-alignment.md](references/effect-alignment.md) covers how a
handler's chain keeps its types consistent.

## Red flags

- A workflow that can refuse, with no signature that states its failures
- A step, or the workflow, takes a capability or has an effect type
- A step takes and returns the same type
- Validation happens in two steps of the same pipeline
- A step writes to the database and also decides business rules
- The order of steps is enforced only by the order of the lines
- The workflow returns nothing and sends notifications itself
- Steps named `process`, `handle`, `execute`, `doWork`

## Common mistakes

- **Making steps too small.** Ten steps of two lines each is ceremony;
  the reader now assembles the process from fragments.
- **Reusing one type through the whole pipeline.** Then no step is
  protected from receiving unprepared data.
- **Letting a step perform side effects it decided on.** Return the
  decision, let the shell act, or the workflow cannot be tested or
  retried.
- **Unifying step errors by casting instead of lifting.** Each step
  keeps its own narrow error, and the pipeline lifts each into the
  workflow's one choice type with `mapError`, a case per failure.
- **Putting the pipeline in the controller.** The controller translates
  transport; the workflow is domain code and belongs with the domain.

## Related skills

- [functional-composing-functions](../functional-composing-functions/SKILL.md)
- [functional-handling-errors-with-results](../functional-handling-errors-with-results/SKILL.md)
- [functional-parameterizing-dependencies](../functional-parameterizing-dependencies/SKILL.md)
- [functional-separating-pure-core-from-shell](../functional-separating-pure-core-from-shell/SKILL.md)

## Further reading

- [pipeline-anatomy.md](references/pipeline-anatomy.md) details each part
  and how to size the steps.
- [effect-alignment.md](references/effect-alignment.md) covers joining
  steps that can fail with steps that cannot, and the handler's chain of
  reads and writes.
- [worked-example.md](references/worked-example.md) builds a complete
  workflow end to end, from event storming output to composed code.

Files in this skill

  • SKILL.md10.6 KB
  • references/effect-alignment.md4.9 KB
  • references/pipeline-anatomy.md5.9 KB
  • references/worked-example.md11.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.