---
title: "Quick Start"
description: "Build the same order-processing Flow as Why Dex?: install Dex, register a Worker and Client, start the Flow from an HTTP controller, approve shipment, and inspect the Execution graph in Dex Web."
canonical_url: "https://docs.superdurable.io/quick-start/"
source: "docs/content/quick-start/index.mdx"
locale: "en"
---

# Quick Start

Build the same order-processing Flow as [Why Dex?](https://docs.superdurable.io/intro/what-is-dex.md): install Dex, register a Worker and Client, start the Flow from an HTTP controller, approve shipment, and inspect the Execution graph in Dex Web.

## 1. Install Dex

```bash
brew install superdurable/tap/dexcli
brew update && brew upgrade superdurable/tap/dexcli   # when you want the latest
dexcli dev
```

**dexcli** starts Dex Server, Dex Web, and the internal workflow backend. Defaults
when those ports are free:

| Service    | Address                     |
| ---------- | --------------------------- |
| Dex Web    | **<http://127.0.0.1:8802>** |
| Dex Server | **127.0.0.1:8801**          |

A second **dexcli dev** on the same machine binds other free ports and uses its
own SQLite database. Use the printed Dex Server address with
**--server**. See the [CLI README](https://github.com/superdurable/dex/blob/main/cli/README.md)
for flags and persistence options.

## 2. Flow, Steps, RPCs, constructor dependency

Charge, then wait for seller approval (Channel or reminder Timer), then ship. Exhausted ship retries go to Refund. The design is on [Why Dex?](https://docs.superdurable.io/intro/what-is-dex.md).

Pass the mock payment and shipping API into the Flow constructor. Do not use a setter or a global.

**Python**

```python
order_status = Attribute(
    "order-status",
    str,
    AttributeIndex(IndexType.KEYWORD, "keyword1"),
)
seller_ok = Channel[str]("seller-ok", str)


class ChargeStep(Step[OrderRequest]):
    def __init__(self, service: MyDependencyService, ship: ShipStep) -> None:
        self.service = service
        self.ship = ship

    def get_step_options(self) -> StepOptions:
        return StepOptions(
            execute_retry=RetryPolicy(
                total_duration=timedelta(hours=1),
            )
        )

    def execute(self, context: Context, input: OrderRequest) -> StepDecision:
        self.service.charge_user(input.email, input.customer_id, input.amount)
        order_status.set(context, "charged")
        return go_to(ShipStep, input)


class ShipStep(Step[OrderRequest]):
    def __init__(self, service: MyDependencyService, refund: RefundStep) -> None:
        self.service = service
        self.refund = refund

    def get_step_options(self) -> StepOptions:
        return StepOptions(
            execute_retry=RetryPolicy(
                total_duration=timedelta(hours=1),
            )
        ).on_execute_failure_proceed_to(
            RefundStep,
            StepOptions(
                execute_retry=RetryPolicy(
                    total_duration=timedelta(hours=1),
                )
            ),
        )

    def wait_for(self, context: Context, input: OrderRequest) -> Wait:
        return Wait.any_of(
            seller_ok.for_one(),
            Timer.by_duration(timedelta(hours=24)),
        )

    def execute(self, context: Context, input: OrderRequest) -> StepDecision:
        if context.has_timer_fired():
            self.service.send_email(
                input.email,
                "Reminder: approve shipment",
                "Please approve or provide a tracking number.",
            )
            return go_to(ShipStep, input)
        self.service.ship_item(input.order_id)
        order_status.set(context, "shipped")
        return graceful_complete(f"shipped:{input.order_id}")


class RefundStep(Step[OrderRequest]):
    def __init__(self, service: MyDependencyService) -> None:
        self.service = service

    def execute(self, context: Context, input: OrderRequest) -> StepDecision:
        self.service.update_external_system(f"refund {input.order_id}")
        order_status.set(context, "refunded")
        return graceful_complete(f"refunded:{input.order_id}")


class OrderProcessingFlow(Flow[OrderRequest]):
    def __init__(self, service: MyDependencyService) -> None:
        self.service = service
        self.refund = RefundStep(service)
        self.ship = ShipStep(service, self.refund)
        self.charge = ChargeStep(service, self.ship)

    def get_steps(self) -> StepList[OrderRequest]:
        return StepList.start_step(self.charge).other_steps(self.ship, self.refund)

    def get_persistence_schema(self) -> PersistenceSchema:
        return PersistenceSchema.of(order_status, seller_ok)

    @rpc
    def approve(self, context: Context, _note: str) -> RPCResult[str]:
        seller_ok.publish(context, "approved")
        return RPCResult("ok")

    @rpc
    def describe(self, context: Context) -> RPCResult[str]:
        return RPCResult(order_status.get(context))
```

Example: [examples/python/dex_examples/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/products/order-processing)

**Go**

```go
type OrderProcessingFlow struct {
	dex.FlowDefaults
	service service.MyService
}

func NewOrderProcessingFlow(applicationService service.MyService) *OrderProcessingFlow {
	return &OrderProcessingFlow{service: applicationService}
}

func (flow *OrderProcessingFlow) GetSteps() []dex.StepDef {
	return []dex.StepDef{
		dex.DefineStartStep(chargeStep{service: flow.service}),
		dex.DefineStep(shipStep{service: flow.service}),
		dex.DefineStep(refundStep{service: flow.service}),
	}
}

func (flow *OrderProcessingFlow) GetRPCs() []dex.RPCDef {
	return []dex.RPCDef{
		dex.DefineRPC(flow.Approve, nil),
		dex.DefineRPC(flow.Describe, nil),
	}
}

func (*OrderProcessingFlow) GetPersistenceSchema() dex.PersistenceSchema {
	return dex.PersistenceSchema{
		Attributes: []dex.AttributeDef{OrderStatus},
		Channels:   []dex.ChannelDef{SellerOK},
	}
}

func (*OrderProcessingFlow) Approve(
	ctx dex.Context,
	_ string,
) (*dex.RPCResult[string], error) {
	if err := SellerOK.Publish(ctx, "approved"); err != nil {
		return nil, err
	}
	return &dex.RPCResult[string]{Output: "ok"}, nil
}

func (*OrderProcessingFlow) Describe(
	ctx dex.Context,
	_ dex.None,
) (*dex.RPCResult[string], error) {
	status, err := OrderStatus.Get(ctx)
	if err != nil {
		return nil, err
	}
	return &dex.RPCResult[string]{Output: status}, nil
}
```

```go
func (chargeStep) GetStepOptions() *dex.StepOptions {
	return &dex.StepOptions{
		ExecuteRetry: &dex.RetryPolicy{
			TotalDuration: time.Hour,
		},
	}
}

func (step chargeStep) Execute(
	ctx dex.Context,
	order OrderRequest,
) (*dex.StepDecision, error) {
	step.service.ChargeUser(order.Email, order.CustomerID, order.Amount)
	if err := OrderStatus.Set(ctx, "charged"); err != nil {
		return nil, err
	}
	return dex.GoTo(shipStep{}, order), nil
}

type shipStep struct {
	dex.DefaultStepType
	service service.MyService
}

func (shipStep) GetStepType() string {
	return ShipStepType
}

func (step shipStep) GetStepOptions() *dex.StepOptions {
	return &dex.StepOptions{
		ExecuteRetry: &dex.RetryPolicy{
			TotalDuration: time.Hour,
		},
		ExecuteFailure: dex.ProceedToOnExecuteFailure(
			refundStep{},
			&dex.StepOptions{
				ExecuteRetry: &dex.RetryPolicy{
					TotalDuration: time.Hour,
				},
			},
		),
	}
}

func (shipStep) WaitFor(
	dex.Context,
	OrderRequest,
) (*dex.Wait, error) {
	return dex.AnyOf(
		SellerOK.ForOne(),
		dex.Timer(24 * time.Hour),
	), nil
}

func (step shipStep) Execute(
	ctx dex.Context,
	order OrderRequest,
) (*dex.StepDecision, error) {
	if ctx.HasTimerFired() {
		step.service.SendEmail(
			order.Email,
			"Reminder: approve shipment",
			"Please approve or provide a tracking number.",
		)
		return dex.GoTo(shipStep{}, order), nil
	}
	if err := step.service.ShipItem(order.OrderID); err != nil {
		return nil, err
	}
	if err := OrderStatus.Set(ctx, "shipped"); err != nil {
		return nil, err
	}
	return dex.GracefulComplete("shipped:" + order.OrderID), nil
}

type refundStep struct {
	dex.StepDefaultsNoWaitFor[OrderRequest]
	service service.MyService
}

func (refundStep) GetStepType() string {
	return RefundStepType
}

func (step refundStep) Execute(
	ctx dex.Context,
	order OrderRequest,
) (*dex.StepDecision, error) {
	step.service.UpdateExternalSystem("refund " + order.OrderID)
	if err := OrderStatus.Set(ctx, "refunded"); err != nil {
		return nil, err
	}
	return dex.GracefulComplete("refunded:" + order.OrderID), nil
}
```

Example: [examples/go/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/go/products/order-processing)

**Java**

```java
public class OrderProcessingFlow implements Flow<OrderRequest> {
    public static final Attribute<String> orderStatus = Attribute.define(
            "order-status",
            String.class,
            new AttributeIndex(AttributeIndex.Type.KEYWORD, "keyword1"));
    public static final Channel<String> sellerOk = Channel.define("seller-ok", String.class);

    private final MyDependencyService service;
    private final ChargeStep charge;
    private final ShipStep ship;
    private final RefundStep refund;

    public OrderProcessingFlow(final MyDependencyService service) {
        this.service = service;
        this.refund = new RefundStep(service);
        this.ship = new ShipStep(service, this.refund);
        this.charge = new ChargeStep(service, this.ship);
    }

    @Override
    public StepList<OrderRequest> getSteps() {
        return StepList.startStep(charge).otherSteps(ship, refund);
    }

    @Override
    public PersistenceSchema getPersistenceSchema() {
        return PersistenceSchema.of(orderStatus, sellerOk);
    }

    @RPC
    public RPCResult<String> approve(final Context context, final String note) {
        sellerOk.publish(context, "approved");
        return RPCResult.of("ok");
    }

    @RPC
    public RPCResult<String> describe(final Context context) {
        return RPCResult.of(orderStatus.get(context));
    }

    static final class ChargeStep implements Step<OrderRequest> {
        private final MyDependencyService service;
        private final ShipStep ship;

        ChargeStep(final MyDependencyService service, final ShipStep ship) {
            this.service = service;
            this.ship = ship;
        }

        @Override
        public String getStepType() {
            return "ChargeStep";
        }

        @Override
        public Class<OrderRequest> getInputType() {
            return OrderRequest.class;
        }

        @Override
        public StepOptions getStepOptions() {
            return StepOptions.newBuilder()
                    .executeRetry(RetryPolicy.newBuilder()
                            .totalDuration(Duration.ofHours(1))
                            .build())
                    .build();
        }

        @Override
        public StepDecision execute(final Context context, final OrderRequest order) {
            service.chargeUser(order.email, order.customerId, order.amount);
            orderStatus.set(context, "charged");
            return StepDecision.goTo(ShipStep.class, order);
        }
    }

    static final class ShipStep implements Step<OrderRequest> {
        private final MyDependencyService service;
        private final RefundStep refund;

        ShipStep(final MyDependencyService service, final RefundStep refund) {
            this.service = service;
            this.refund = refund;
        }

        @Override
        public String getStepType() {
            return "ShipStep";
        }

        @Override
        public Class<OrderRequest> getInputType() {
            return OrderRequest.class;
        }

        @Override
        public StepOptions getStepOptions() {
            return StepOptions.newBuilder()
                    .executeRetry(RetryPolicy.newBuilder()
                            .totalDuration(Duration.ofHours(1))
                            .build())
                    .onExecuteFailureProceedTo(
                            RefundStep.class,
                            StepOptions.newBuilder()
                                    .executeRetry(RetryPolicy.newBuilder()
                                            .totalDuration(Duration.ofHours(1))
                                            .build())
                                    .build())
                    .build();
        }

        @Override
        public Wait waitFor(final Context context, final OrderRequest order) {
            return Wait.anyOf(
                    sellerOk.forOne(),
                    Timer.byDuration(Duration.ofHours(24)));
        }

        @Override
        public StepDecision execute(final Context context, final OrderRequest order) {
            if (context.hasTimerFired()) {
                service.sendEmail(
                        order.email,
                        "Reminder: approve shipment",
                        "Please approve or provide a tracking number.");
                return StepDecision.goTo(ShipStep.class, order);
            }
            service.shipItem(order.orderId);
            orderStatus.set(context, "shipped");
            return StepDecision.gracefulComplete("shipped:" + order.orderId);
        }
    }

    static final class RefundStep implements Step<OrderRequest> {
        private final MyDependencyService service;

        RefundStep(final MyDependencyService service) {
            this.service = service;
        }

        @Override
        public String getStepType() {
            return "RefundStep";
        }

        @Override
        public Class<OrderRequest> getInputType() {
            return OrderRequest.class;
        }

        @Override
        public StepDecision execute(final Context context, final OrderRequest order) {
            service.updateExternalSystem("refund " + order.orderId);
            orderStatus.set(context, "refunded");
            return StepDecision.gracefulComplete("refunded:" + order.orderId);
        }
    }
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/products/orderprocessing](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/products/orderprocessing)

**TypeScript**

```typescript
const orderStatus = new Attribute("order-status", stringCodec, {
  type: IndexType.KEYWORD,
  indexKey: "keyword1",
});
const sellerOk = new Channel("seller-ok", stringCodec);

export class OrderProcessingFlow implements Flow<OrderRequest> {
  public readonly charge: ChargeStep;
  public readonly ship: ShipStep;
  public readonly refund: RefundStep;

  public constructor(public readonly service: MyDependencyService) {
    this.refund = new RefundStep(service);
    this.ship = new ShipStep(service, this.refund);
    this.charge = new ChargeStep(service, this.ship);
  }

  public getFlowType(): string {
    return "OrderProcessingFlow";
  }

  public getSteps() {
    return StepList.startStep(this.charge).otherSteps(this.ship, this.refund);
  }

  public getPersistenceSchema(): PersistenceSchema {
    return {
      attributes: [orderStatus],
      channels: [sellerOk],
    };
  }

  @rpc({ inputCodec: stringCodec, outputCodec: stringCodec })
  public approve(context: Context, _note: string): RPCResult<string> {
    sellerOk.publish(context, "approved");
    return { output: "ok" };
  }

  @rpc({ outputCodec: stringCodec })
  public describe(context: Context): RPCResult<string> {
    return { output: orderStatus.get(context) };
  }
}

class ChargeStep implements Step<OrderRequest> {
  public readonly inputCodec = orderRequestCodec;

  public constructor(
    private readonly service: MyDependencyService,
    private readonly ship: ShipStep,
  ) {}

  public getStepType(): string {
    return "ChargeStep";
  }

  public getStepOptions() {
    return {
      executeRetry: {
        totalDurationMs: 60 * 60 * 1000,
      },
    };
  }

  public execute(context: Context, input: OrderRequest): StepDecision {
    this.service.chargeUser(input.email, input.customerId, input.amount);
    orderStatus.set(context, "charged");
    return goTo(ShipStep, input);
  }
}

class ShipStep implements Step<OrderRequest> {
  public readonly inputCodec = orderRequestCodec;

  public constructor(
    private readonly service: MyDependencyService,
    private readonly refund: RefundStep,
  ) {}

  public getStepType(): string {
    return "ShipStep";
  }

  public getStepOptions() {
    return {
      executeRetry: {
        totalDurationMs: 60 * 60 * 1000,
      },
      executeFailure: ExecuteFailure.proceedTo(RefundStep, {
        executeRetry: {
          totalDurationMs: 60 * 60 * 1000,
        },
      }),
    };
  }

  public waitFor(_context: Context, _input: OrderRequest): Wait {
    return Wait.anyOf(sellerOk.forOne(), Timer.byDuration(DAY_MS));
  }

  public execute(context: Context, input: OrderRequest): StepDecision {
    if (context.hasTimerFired()) {
      this.service.sendEmail(
        input.email,
        "Reminder: approve shipment",
        "Please approve or provide a tracking number.",
      );
      return goTo(ShipStep, input);
    }
    this.service.shipItem(input.orderId);
    orderStatus.set(context, "shipped");
    return gracefulComplete(`shipped:${input.orderId}`);
  }
}

class RefundStep implements Step<OrderRequest> {
  public readonly inputCodec = orderRequestCodec;

  public constructor(private readonly service: MyDependencyService) {}

  public getStepType(): string {
    return "RefundStep";
  }

  public execute(context: Context, input: OrderRequest): StepDecision {
    this.service.updateExternalSystem(`refund ${input.orderId}`);
    orderStatus.set(context, "refunded");
    return gracefulComplete(`refunded:${input.orderId}`);
  }
}
```

Example: [examples/typescript/src/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/order-processing)

**Rust**

```rust
#[derive(Clone)]
pub struct OrderProcessingFlow {
    charge: Charge,
    ship: Ship,
    refund: Refund,
}

impl OrderProcessingFlow {
    pub fn new(service: MyDependencyService) -> Self {
        Self {
            charge: Charge {
                service: service.clone(),
            },
            ship: Ship {
                service: service.clone(),
            },
            refund: Refund { service },
        }
    }

    fn approve(&self, context: &mut Context, _note: String) -> HandlerResult<RpcResult<String>> {
        SELLER_OK.publish(context, "approved".to_string())?;
        Ok(RpcResult::new("ok".to_string()))
    }

    fn describe(&self, context: &mut Context) -> HandlerResult<RpcResult<String>> {
        Ok(RpcResult::new(ORDER_STATUS.get_required(context)?))
    }
}

impl Default for OrderProcessingFlow {
    fn default() -> Self {
        Self::new(MyDependencyService)
    }
}

impl Flow for OrderProcessingFlow {
    type StartInput = OrderRequest;

    fn flow_type(&self) -> &'static str {
        "OrderProcessingFlow"
    }

    fn steps(&self) -> StepList<'_, Self::StartInput> {
        StepList::start(&self.charge)
            .and(&self.ship)
            .and(&self.refund)
    }

    fn persistence(&self) -> PersistenceSchema {
        PersistenceSchema::new()
            .attribute(&ORDER_STATUS)
            .channel(&SELLER_OK)
    }

    fn rpcs(&self) -> RpcList<Self> {
        RpcList::new()
            .function(APPROVE_ORDER, Self::approve)
            .function_without_input(DESCRIBE_ORDER, Self::describe)
    }
}

#[derive(Clone, Default)]
pub struct Charge {
    service: MyDependencyService,
}

impl Step for Charge {
    type Input = OrderRequest;

    fn step_type(&self) -> &'static str {
        "ChargeStep"
    }

    fn options(&self) -> StepOptions<Self::Input> {
        StepOptions::new().execute_retry(
            RetryPolicy::new().total_duration(Duration::from_secs(60 * 60)),
        )
    }

    fn execute(&self, context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        self.service
            .charge_user(&input.email, &input.customer_id, input.amount);
        context.record_event("charge", input.order_id.clone())?;
        ORDER_STATUS.set(context, "charged".to_string())?;
        Ok(StepDecision::go_to(&Ship::default(), input))
    }
}

#[derive(Clone, Default)]
pub struct Ship {
    service: MyDependencyService,
}

impl Step for Ship {
    type Input = OrderRequest;

    fn step_type(&self) -> &'static str {
        "ShipStep"
    }

    fn options(&self) -> StepOptions<Self::Input> {
        StepOptions::new()
            .execute_retry(
                RetryPolicy::new().total_duration(Duration::from_secs(60 * 60)),
            )
            .on_execute_failure_proceed_to(&Refund::default())
    }

    fn wait_for(&self, _context: &mut Context, _input: Self::Input) -> HandlerResult<Wait> {
        Ok(Wait::any_of([
            SELLER_OK.for_one(),
            Timer::by_duration(Duration::from_secs(24 * 60 * 60)),
        ]))
    }

    fn execute(&self, context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        if context.has_any_timer_fired() {
            self.service.send_email(
                &input.email,
                "Reminder: approve shipment",
                "Please approve or provide a tracking number.",
            );
            context.record_event("shipment-reminder", input.order_id.clone())?;
            return Ok(StepDecision::go_to(&Ship::default(), input));
        }
        self.service.ship_item(&input.order_id)?;
        context.record_event("ship", input.order_id.clone())?;
        ORDER_STATUS.set(context, "shipped".to_string())?;
        Ok(StepDecision::graceful_complete(format!(
            "shipped:{}",
            input.order_id
        )))
    }
}

#[derive(Clone, Default)]
pub struct Refund {
    service: MyDependencyService,
}

impl Step for Refund {
    type Input = OrderRequest;

    fn step_type(&self) -> &'static str {
        "RefundStep"
    }

    fn options(&self) -> StepOptions<Self::Input> {
        StepOptions::new().execute_retry(
            RetryPolicy::new().total_duration(Duration::from_secs(60 * 60)),
        )
    }

    fn execute(&self, context: &mut Context, input: Self::Input) -> HandlerResult<StepDecision> {
        self.service
            .update_external_system(&format!("refund {}", input.order_id));
        context.record_event("refund", input.order_id.clone())?;
        ORDER_STATUS.set(context, "refunded".to_string())?;
        Ok(StepDecision::graceful_complete(format!(
            "refunded:{}",
            input.order_id
        )))
    }
}

static ORDER_STATUS: LazyLock<Attribute<String>> = LazyLock::new(|| {
    Attribute::new("order-status").indexed(AttributeIndex::keyword().with_key("keyword1"))
});

static SELLER_OK: LazyLock<Channel<String>> = LazyLock::new(|| Channel::new("seller-ok"));
```

Example: [examples/rust/src/products/order_processing](https://github.com/superdurable/dex/tree/main/examples/rust/src/products/order_processing)

## 3. Worker and Client

Point a Registry, Worker, and Client at **dexcli** (**127.0.0.1:8801**). Construct the Flow with the service, then register it.

The Worker opens outbound task streams to **dexcli** and needs no inbound port.
See [Workers and the task queue](https://docs.superdurable.io/reference/server/workers.md) for capacity, readiness, and
production setup.

**Python**

```python
        service = MyDependencyService()
        pattern_service = ServiceDependency()

        self.money_transfer = MoneyTransferFlow(service)
        self.order_processing = OrderProcessingFlow(service)
```

```python
        self.registry = Registry(tuple(flows), allow_async_handlers=True)
        config.blob_cache_dir.mkdir(parents=True, exist_ok=True)
        self.blob_cache = open_blob_cache(
            BlobCacheConfig(str(config.blob_cache_dir), 1 << 30)
        )
        self.worker = AsyncWorker(
            self.registry,
            self.blob_cache,
            WorkerOptions(server_address=config.server_address),
        )
        self._client = AsyncClient(
            self.registry,
            self.blob_cache,
            ClientOptions(server_address=config.server_address),
        )
```

Example: [examples/python/dex_examples/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/products/order-processing)

**Go**

```go
	var client *sdk.Client
	flows := registry.New(service.NewMyService(), func() *sdk.Client { return client })
	flowRegistry, err := sdk.NewRegistry(flows)
	if err != nil {
		return nil, errors.Join(fmt.Errorf("register example flows: %w", err), cache.Close())
	}
	worker, err := sdk.NewWorker(flowRegistry, cache, sdk.WorkerOptions{
		FlowServiceAddress: os.Getenv("DEX_FLOW_SERVICE_ADDRESS"),
	})
	if err != nil {
		return nil, errors.Join(err, cache.Close())
	}
	client, err = sdk.NewClient(flowRegistry, cache, sdk.ClientOptions{
		FlowServiceAddress: os.Getenv("DEX_FLOW_SERVICE_ADDRESS"),
	})
```

```go
	orderprocessing.RegisterRoutes(router, client, registry.OrderProcessing)
```

Example: [examples/go/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/go/products/order-processing)

**Java**

```java
    @Bean(destroyMethod = "close")
    public Worker worker(
            final Registry registry,
            final BlobCache blobCache,
            @Value("${dex.server-address}") final String serverAddress) {
        return new Worker(registry, blobCache, WorkerOptions.newBuilder()
                .serverAddress(serverAddress)
                .build());
    }

    @Bean(destroyMethod = "close")
    public Client client(
            final Registry registry,
            final BlobCache blobCache,
            @Value("${dex.server-address}") final String serverAddress) {
        return new Client(registry, blobCache, new ClientOptions(serverAddress));
    }
```

Example: [examples/java/src/main/java/io/superdurable/dex/products/orderprocessing](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/products/orderprocessing)

**TypeScript**

```typescript
export const orderProcessingFlow = new OrderProcessingFlow(new MyDependencyService());
```

```typescript
  const registry = createExampleRegistry();
  const blobCache = openBlobCache({
    directory: env.blobCacheDir,
    maxBytes: 1 << 30,
  });
  const worker = await startExampleWorker(registry, blobCache, env.serverAddress);
  const client = new Client(registry, blobCache, {
    serverAddress: env.serverAddress,
  });
  setClient(client);
```

```typescript
  app.use("/products/order-processing", createOrderProcessingRouter(client, orderProcessingFlow));
```

Example: [examples/typescript/src/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/order-processing)

**Rust**

```rust
    let client = Arc::new(Client::try_new(
        create_example_registry(Arc::clone(&polling_jobs))?,
        Arc::clone(&cache),
        ClientOptions::new().server_address(config.server_address.clone()),
    )?);
    let worker = Arc::new(Worker::try_new(
        create_worker_registry(Arc::clone(&client), Arc::clone(&polling_jobs))?,
        Arc::clone(&cache),
        WorkerOptions::new().server_address(&config.server_address),
    )?);
    let running_worker = Arc::clone(&worker);
    let worker_handle = thread::spawn(move || running_worker.start());
    if let Err(error) = wait_for_pull_registration(&worker).await {
        worker.stop();
        if let Err(stop_error) = worker_handle.join().expect("worker thread panicked") {
            eprintln!("pull Worker stopped with an error: {stop_error}");
        }
        return Err(error);
    }

    let router = build_router(client, polling_jobs);
```

Example: [examples/rust/src/products/order_processing](https://github.com/superdurable/dex/tree/main/examples/rust/src/products/order_processing)

## 4. Controller

The start handler starts the Flow, then waits for ChargeStep with the Client wait-for-Step-completion API. Approve publishes the seller Channel. Describe reads **order-status**. Client and Flow come from the constructor or factory args.

**Python**

```python
    @blueprint.get("/start")
    async def start() -> Response:
        flow_id = new_flow_id("order-processing")
        request = OrderRequest(
            flow_id,
            "buyer@example.com",
            "customer-1",
            42,
        )
        run_id = await app_state.client.start_flow(
            app_state.order_processing,
            flow_id,
            request,
            start_options(),
        )
        await app_state.client.wait_for_step_completion(
            flow_id,
            CHARGE_STEP,
            CHARGE_WAIT_TIMEOUT,
        )
        return started_flow(flow_id, run_id)

    @blueprint.get("/approve")
    async def approve() -> Response:
        output = await app_state.client.invoke_rpc(
            app_state.order_processing.approve,
            required_query("workflowId"),
            optional_query("notes", ""),
        )
        return jsonify(output)

    @blueprint.get("/describe")
    async def describe() -> Response:
        flow_id = required_query("workflowId")
        status = await app_state.client.invoke_rpc(
            app_state.order_processing.describe,
            flow_id,
        )
        return jsonify({"flowID": flow_id, "status": status})
```

Example: [examples/python/dex_examples/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/python/dex_examples/products/order-processing)

**Go**

```go
func RegisterRoutes(router gin.IRouter, client *sdk.Client, flow *OrderProcessingFlow) {
	controller := &controller{client: client, flow: flow}
	group := router.Group("/products/order-processing")
	group.GET("/start", controller.start)
	group.GET("/approve", controller.approve)
	group.GET("/describe", controller.describe)
}

func (controller *controller) start(request *gin.Context) {
	flowID := httputil.NewFlowID("order-processing")
	input := OrderRequest{
		OrderID:    flowID,
		Email:      "buyer@example.com",
		CustomerID: "customer-1",
		Amount:     42,
	}
	runID, err := controller.client.StartFlow(
		request.Request.Context(),
		controller.flow,
		flowID,
		input,
		sdk.StartFlowOptions{},
	)
	if err != nil {
		httputil.Respond(request, nil, err)
		return
	}
	err = controller.client.WaitForStepCompletion(
		request.Request.Context(),
		flowID,
		sdk.StepExecutionID{StepType: ChargeStepType},
		sdk.WaitOptions{Timeout: 5 * time.Minute},
	)
	httputil.Respond(request, gin.H{"flowID": flowID, "runID": runID}, err)
}

func (controller *controller) approve(request *gin.Context) {
	flowID, found := httputil.RequiredQuery(request, "workflowId")
	if !found {
		return
	}
	var output string
	err := controller.client.InvokeRPC(
		request.Request.Context(),
		flowID,
		controller.flow.Approve,
		request.Query("notes"),
		&output,
	)
	httputil.Respond(request, output, err)
}

func (controller *controller) describe(request *gin.Context) {
	flowID, found := httputil.RequiredQuery(request, "workflowId")
	if !found {
		return
	}
	var output string
	err := controller.client.InvokeRPC(
		request.Request.Context(),
		flowID,
		controller.flow.Describe,
		nil,
		&output,
	)
	httputil.Respond(request, gin.H{"flowID": flowID, "status": output}, err)
}
```

Example: [examples/go/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/go/products/order-processing)

**Java**

```java
@RestController
@RequestMapping("/products/order-processing")
public class OrderProcessingController {
    private final Client client;
    private final OrderProcessingFlow flow;

    public OrderProcessingController(
            final Client client,
            final OrderProcessingFlow flow) {
        this.client = client;
        this.flow = flow;
    }

    @GetMapping("/start")
    public ResponseEntity<Map<String, String>> start() {
        final String flowId = "order-processing-" + System.nanoTime();
        final OrderRequest request = new OrderRequest(
                flowId,
                "buyer@example.com",
                "customer-1",
                42);
        final String runId = client.startFlow(flow, flowId, request, ExampleFlows.startOptions());
        client.waitForStepCompletion(
                flowId,
                StepExecutionId.of("ChargeStep"),
                Duration.ofMinutes(5));
        final Map<String, String> response = new LinkedHashMap<String, String>();
        response.put("flowID", flowId);
        response.put("runID", runId);
        return ResponseEntity.ok(response);
    }

    @GetMapping("/approve")
    public ResponseEntity<String> approve(
            @RequestParam final String workflowId,
            @RequestParam(defaultValue = "") final String notes) {
        final OrderProcessingFlow stub = client.newRpcStub(OrderProcessingFlow.class, workflowId);
        return ResponseEntity.ok(client.invokeRPC(stub::approve, notes));
    }

    @GetMapping("/describe")
    public ResponseEntity<Map<String, String>> describe(
            @RequestParam final String workflowId) {
        final OrderProcessingFlow stub = client.newRpcStub(OrderProcessingFlow.class, workflowId);
        final String status = client.invokeRPC(stub::describe);
        final Map<String, String> response = new LinkedHashMap<String, String>();
        response.put("flowID", workflowId);
        response.put("status", status);
        return ResponseEntity.ok(response);
    }
}
```

Example: [examples/java/src/main/java/io/superdurable/dex/products/orderprocessing](https://github.com/superdurable/dex/tree/main/examples/java/src/main/java/io/superdurable/dex/products/orderprocessing)

**TypeScript**

```typescript
export function createOrderProcessingRouter(
  client: Client,
  flow: OrderProcessingFlow,
): Router {
  const router = Router();

  router.get("/start", async (request, response) => {
    const flowId = `order-processing-${process.hrtime.bigint()}`;
    const input: OrderRequest = {
      orderId: flowId,
      email: "buyer@example.com",
      customerId: "customer-1",
      amount: 42,
    };
    const runId = await client.startFlow(flow, flowId, input, startOptions());
    await client.waitForStepCompletion(
      flowId,
      StepExecutionId.of("ChargeStep"),
      5 * 60 * 1000,
    );
    response.json({ flowID: flowId, runID: runId });
  });

  router.get("/approve", async (request, response) => {
    const workflowId = String(request.query.workflowId ?? "");
    const notes = String(request.query.notes ?? "");
    const output = await client.invokeRPC(flow.approve, workflowId, notes);
    response.json(output);
  });

  router.get("/describe", async (request, response) => {
    const workflowId = String(request.query.workflowId ?? "");
    const status = await client.invokeRPC(flow.describe, workflowId);
    response.json({ flowID: workflowId, status });
  });

  return router;
}
```

Example: [examples/typescript/src/products/order-processing](https://github.com/superdurable/dex/tree/main/examples/typescript/src/products/order-processing)

**Rust**

```rust
pub fn mount(client: SharedClient, flow: OrderProcessingFlow) -> Router {
    Router::new()
        .route("/products/order-processing/start", get(start))
        .route("/products/order-processing/approve", get(approve))
        .route("/products/order-processing/describe", get(describe))
        .with_state(OrderProcessingState { client, flow })
}

async fn start(
    State(state): State<OrderProcessingState>,
) -> impl IntoResponse {
    let OrderProcessingState { client, flow } = state;
    match run_blocking(move || {
        let flow_id = new_flow_id("order-processing");
        let input = OrderRequest {
            order_id: flow_id.clone(),
            email: "buyer@example.com".into(),
            customer_id: "customer-1".into(),
            amount: 42,
        };
        let run_id = client.start_flow(&flow, &flow_id, input)?;
        client.wait_for_step_completion(
            &flow_id,
            StepExecutionId::of(&Charge::default()),
            Duration::from_secs(300),
        )?;
        Ok(StartResponse { flow_id, run_id })
    }) {
        Ok(value) => ok_json(value),
        Err(error) => map_sdk_error(error).into_response(),
    }
}

async fn approve(
    State(state): State<OrderProcessingState>,
    Query(query): Query<WorkflowQuery>,
) -> impl IntoResponse {
    let flow_id = query.workflow_id;
    let notes = query.notes;
    match run_blocking(move || state.client.invoke_rpc(&flow_id, APPROVE_ORDER, notes)) {
        Ok(value) => ok_json(value),
        Err(error) => map_sdk_error(error).into_response(),
    }
}

async fn describe(
    State(state): State<OrderProcessingState>,
    Query(query): Query<WorkflowQuery>,
) -> impl IntoResponse {
    let flow_id = query.workflow_id;
    match run_blocking(move || {
        state
            .client
            .invoke_rpc_without_input(&flow_id, DESCRIBE_ORDER)
            .map(|status| json!({ "flowID": flow_id, "status": status }))
    }) {
        Ok(value) => ok_json(value),
        Err(error) => map_sdk_error(error).into_response(),
    }
}
```

Example: [examples/rust/src/products/order_processing](https://github.com/superdurable/dex/tree/main/examples/rust/src/products/order_processing)

## 5. Run

Keep **dexcli dev** running. In another terminal:

**Python**

```bash
cd examples/python
uv run python main.py
```

**Go**

```bash
cd examples/go
make bins
./dex-samples
```

**Java**

```bash
cd examples/java
./gradlew bootRun
```

**TypeScript**

```bash
cd examples/typescript
npm start
```

**Rust**

```bash
cd examples/rust
cargo run --locked
```

Then start an order, open Dex Web, and approve shipment:

```bash
curl -s 'http://127.0.0.1:8080/products/order-processing/start'
```

Copy **flowID** from the JSON. Open <http://127.0.0.1:8802>, find that Flow, and open its details. ShipStep is waiting.

```bash
curl -s "http://127.0.0.1:8080/products/order-processing/approve?workflowId=FLOW_ID"
```

Replace **FLOW_ID** with the start response. The Flow ships and completes.

## 6. Dex Web

After start and approve, open the Flow. The Execution graph shows Charge then Ship:

![Dex Web Execution graph after approve](https://docs.superdurable.io/img/quick-start/step-graph.png)

The Timeline tab lists the same run:

![Dex Web Timeline after approve](https://docs.superdurable.io/img/quick-start/timeline.png)

## Next

- [Primitives](https://docs.superdurable.io/primitives.md)
- [Why Dex?](https://docs.superdurable.io/intro/what-is-dex.md)
- [Product Examples](https://docs.superdurable.io/use-cases.md)
