documentation/how-to/api-orchestration.md

# How to Orchestrate HTTP APIs with Reactor

## Problem
You need to integrate with multiple HTTP APIs in production workflows, handling authentication, rate limits, circuit breakers, API versioning, and service discovery patterns.

## Solution Overview
This guide shows you how to build production-ready API orchestration using `reactor_req`, covering real-world concerns like authentication management, circuit breakers, rate limiting, and service resilience patterns.

## Prerequisites
- Understanding of Reactor basics (inputs, steps, arguments)
- Familiarity with error handling and compensation patterns
- Basic knowledge of HTTP APIs and the `Req` HTTP client

## Setup

Add `reactor_req` to your dependencies:

```elixir
# mix.exs
def deps do
  [
    {:reactor, "~> 0.15"},
    {:reactor_req, "~> 0.1"},
    {:req, "~> 0.5"}
  ]
end
```

## HTTP Client Integration with Reactor

The `reactor_req` package provides direct integration between Reactor and the Req HTTP client. Create `lib/api_client.ex`:

```elixir
defmodule ApiClient do
  use Reactor

  input :base_url
  input :user_id

  req_new :setup_client do
    base_url input(:base_url)
    headers value([{"user-agent", "MyApp/1.0"}])
    retry value(:transient)
    retry_delay value(fn attempt -> 200 * attempt end)
  end

  template :build_profile_url do
    argument :user_id, input(:user_id)
    template "/users/<%= @user_id %>"
  end

  template :build_preferences_url do
    argument :user_id, input(:user_id)
    template "/users/<%= @user_id %>/preferences"
  end

  req_get :fetch_profile do
    request result(:setup_client)
    url result(:build_profile_url)
    headers value(%{"accept" => "application/json"})
  end

  req_get :fetch_preferences do
    request result(:setup_client)
    url result(:build_preferences_url)
  end

  step :combine_data do
    argument :profile, result(:fetch_profile, [:body])
    argument :preferences, result(:fetch_preferences, [:body])

    run fn %{profile: profile, preferences: prefs}, _context ->
      {:ok, %{profile: profile, preferences: prefs}}
    end
  end

  return :combine_data
end
```

## Authentication Management

Handle API authentication and token refresh patterns:

```elixir
defmodule AuthenticatedApiClient do
  use Reactor

  input :client_id
  input :client_secret
  input :api_endpoint

  step :build_oauth_payload do
    argument :client_id, input(:client_id)
    argument :client_secret, input(:client_secret)
    
    run fn %{client_id: client_id, client_secret: client_secret}, _context ->
      payload = %{
        grant_type: "client_credentials",
        client_id: client_id,
        client_secret: client_secret
      }
      {:ok, payload}
    end
  end

  req_post :get_auth_token do
    url value("https://auth.example.com/oauth/token")
    json result(:build_oauth_payload)
  end

  step :extract_access_token do
    argument :token_response, result(:get_auth_token, [:body])
    
    run fn %{token_response: resp}, _context ->
      {:ok, resp["access_token"]}
    end
  end

  template :build_auth_header do
    argument :token, result(:extract_access_token)
    template "Bearer <%= @token %>"
  end

  req_new :prepare_authenticated_client do
    base_url input(:api_endpoint)
    headers [{"authorization", result(:build_auth_header)}]
  end

  req_get :fetch_protected_data do
    request result(:prepare_authenticated_client)
    url value("/protected/data")
  end

  return :fetch_protected_data
end
```

## API Versioning

Handle different API versions by composing version-specific reactors:

```elixir
defmodule UserApiV1 do
  use Reactor

  input :user_id

  req_new :client do
    base_url value("https://api.example.com/v1")
  end

  template :build_user_path do
    argument :user_id, input(:user_id)
    template "/users/<%= @user_id %>"
  end

  req_get :fetch_user do
    request result(:client)
    url result(:build_user_path)
  end

  step :normalize_response do
    argument :response, result(:fetch_user, [:body])
    
    run fn %{response: resp}, _context ->
      normalized = %{
        id: resp["user_id"],
        name: resp["full_name"],
        email: resp["email_address"]
      }
      {:ok, normalized}
    end
  end

  return :normalize_response
end

defmodule UserApiV2 do
  use Reactor

  input :user_id

  req_new :client do
    base_url value("https://api.example.com/v2")
  end

  template :build_user_path do
    argument :user_id, input(:user_id)
    template "/users/<%= @user_id %>"
  end

  req_get :fetch_user do
    request result(:client)
    url result(:build_user_path)
  end

  return :fetch_user
end

defmodule VersionedUserApi do
  use Reactor

  input :api_version, default: "v1"
  input :user_id

  switch :fetch_user_by_version do
    on input(:api_version)

    match "v1" do
      compose :get_user, UserApiV1 do
        argument :user_id, input(:user_id)
      end
      return :get_user
    end

    match "v2" do
      compose :get_user, UserApiV2 do
        argument :user_id, input(:user_id)
      end
      return :get_user
    end

    default do
      flunk :unsupported_version do
        argument :version, input(:api_version)
        message "Unsupported API version: <%= @version %>"
      end
    end
  end

  return :fetch_user_by_version
end
```

## Testing API Integration

Test your API orchestration patterns:

```bash
iex -S mix
```

```elixir
# Test basic HTTP client integration
{:ok, result} = Reactor.run(ApiClient, %{
  base_url: "https://jsonplaceholder.typicode.com", 
  user_id: "1"
})

# Test rate limiting
requests = [%{id: 1}, %{id: 2}, %{id: 3}]
{:ok, results} = Reactor.run(RateLimitedApi, %{requests: requests})

# Test versioning
{:ok, normalized} = Reactor.run(VersionedApi, %{
  api_version: "v2",
  user_id: "123"
})
```

## Related Guides

- [Error Handling Tutorial](../tutorials/02-error-handling.md) - Compensation and undo patterns
- [Async Workflows Tutorial](../tutorials/03-async-workflows.md) - Concurrent processing
- [Performance Optimization](performance-optimization.md) - Scaling and monitoring