Skip to content

Existing Instance Migration (Brownfield)

ALPHA RELEASE

This example uses Switchboard v0.1.0-preview.17. The API may change before the stable 1.0 release.

Import and manage an existing Amazon Connect instance with Switchboard.

Overview

This example shows how to migrate an existing Connect instance from console-managed to code-managed infrastructure without disrupting live operations.

Migration strategy: Gradual adoption Risk level: Low (import-only initially) Downtime: Zero Rollback: Easy (remove framework, keep existing resources)


Migration Scenarios

Scenario 1: Import Everything, Manage Nothing

Use case: Start tracking existing resources in code without making changes.

csharp
using Switchboard;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSwitchboard(options =>
{
    // Import existing instance
    options.ImportExistingInstance = true;
    options.ExistingInstanceId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

    // Import only - don't manage anything yet
    options.ManageFlows = false;
    options.ManageQueues = false;
    options.ManageRoutingProfiles = false;
    options.ManageHoursOfOperation = false;
})
.ImportExistingResources(import =>
{
    // Import queues by ARN
    import.AddQueue("Sales", "arn:aws:connect:us-east-1:123:queue/sales-queue-id");
    import.AddQueue("Support", "arn:aws:connect:us-east-1:123:queue/support-queue-id");

    // Import flows
    import.AddFlow("MainInbound", "arn:aws:connect:us-east-1:123:flow/main-inbound-id");
    import.AddFlow("AfterHours", "arn:aws:connect:us-east-1:123:flow/after-hours-id");

    // Import routing profiles
    import.AddRoutingProfile("SalesAgents", "arn:aws:connect:us-east-1:123:routing-profile/sales-id");
});

var app = builder.Build();

Benefits:

  • ✅ Zero risk - no changes to existing instance
  • ✅ Can reference resources in new flows
  • ✅ Establish baseline before managing resources
  • ✅ Easy to remove framework if needed

Scenario 2: Manage Flows Only

Use case: Start managing flows with code, leave queues/profiles in console.

csharp
builder.Services.AddSwitchboard(options =>
{
    options.ImportExistingInstance = true;
    options.ExistingInstanceId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

    // Manage flows, import everything else
    options.ManageFlows = true;          // ✅ Switchboard controls flows
    options.ManageQueues = false;        // ❌ Console controls queues
    options.ManageRoutingProfiles = false;
})
.ImportExistingQueues(import =>
{
    // Reference console-managed queues
    import.AddQueue("Sales", "arn:aws:connect:us-east-1:123:queue/sales-queue-id");
    import.AddQueue("Support", "arn:aws:connect:us-east-1:123:queue/support-queue-id");
})
.AddFlowDefinitions(typeof(Program).Assembly); // New flows managed by code

var app = builder.Build();

New flow can reference imported queues:

csharp
[ContactFlow("NewSalesFlow")]
public partial class NewSalesFlow : FlowDefinitionBase
{
    [Action(Order = 1)]
    [Message("Welcome to our new sales flow")]
    public partial void Welcome();

    [Action(Order = 2)]
    [TransferToQueue("Sales")] // References imported queue
    public partial void TransferToSales();
}

Scenario 3: Full Hybrid Management

Use case: Manage some resources with code, keep critical flows in console.

csharp
builder.Services.AddSwitchboard(options =>
{
    options.ImportExistingInstance = true;
    options.ExistingInstanceId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

    // Selective management
    options.ManageFlows = true;
    options.ManageQueues = true;
    options.ManageRoutingProfiles = false; // Keep in console for now
})
.ImportExistingFlows(import =>
{
    // Critical flows stay in console
    import.AddFlow("MainCustomerService", "arn:aws:connect:...:flow/main-id");
    import.AddFlow("ExecutiveEscalation", "arn:aws:connect:...:flow/exec-id");
})
.ImportExistingQueues(import =>
{
    // These queues will remain managed by console
    import.AddQueue("ExecutiveQueue", "arn:aws:connect:...:queue/exec-id");
})
.AddFlowDefinitions(typeof(Program).Assembly)  // New flows
.AddQueueDefinitions(typeof(Program).Assembly); // New queues

var app = builder.Build();

Complete Migration Example

Step 1: Inventory Existing Resources

bash
# List all flows
aws connect list-contact-flows \
  --instance-id aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \
  --query 'ContactFlowSummaryList[*].[Name,Arn]' \
  --output table

# List all queues
aws connect list-queues \
  --instance-id aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \
  --query 'QueueSummaryList[*].[Name,Arn]' \
  --output table

# List routing profiles
aws connect list-routing-profiles \
  --instance-id aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \
  --query 'RoutingProfileSummaryList[*].[Name,Arn]' \
  --output table

Step 2: Create Import Configuration

src/Migration/ImportConfiguration.cs

csharp
using Switchboard;

namespace ExistingCallCenter.Migration;

public static class ImportConfiguration
{
    public static void ConfigureImports(ISwitchboardImportBuilder import)
    {
        // Import all existing queues
        import.AddQueue("Sales-US-East", "arn:aws:connect:us-east-1:123:queue/sales-us-east-id");
        import.AddQueue("Sales-US-West", "arn:aws:connect:us-east-1:123:queue/sales-us-west-id");
        import.AddQueue("Support-Tier1", "arn:aws:connect:us-east-1:123:queue/support-t1-id");
        import.AddQueue("Support-Tier2", "arn:aws:connect:us-east-1:123:queue/support-t2-id");
        import.AddQueue("Escalation", "arn:aws:connect:us-east-1:123:queue/escalation-id");

        // Import critical flows (don't manage yet)
        import.AddFlow("MainInbound", "arn:aws:connect:us-east-1:123:flow/main-inbound-id");
        import.AddFlow("CustomerService", "arn:aws:connect:us-east-1:123:flow/customer-service-id");
        import.AddFlow("TechnicalSupport", "arn:aws:connect:us-east-1:123:flow/tech-support-id");

        // Import routing profiles
        import.AddRoutingProfile("Sales-Profile", "arn:aws:connect:us-east-1:123:routing-profile/sales-id");
        import.AddRoutingProfile("Support-Profile", "arn:aws:connect:us-east-1:123:routing-profile/support-id");

        // Import hours of operation
        import.AddHoursOfOperation("Business-Hours", "arn:aws:connect:us-east-1:123:hours/business-id");
        import.AddHoursOfOperation("24x7", "arn:aws:connect:us-east-1:123:hours/24x7-id");
    }
}

Step 3: Initial Program Setup

src/Program.cs

csharp
using Amazon.CDK;
using Switchboard;
using ExistingCallCenter.Migration;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSwitchboard(options =>
{
    options.ImportExistingInstance = true;
    options.ExistingInstanceId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

    // Phase 1: Import everything, manage nothing
    options.ManageFlows = false;
    options.ManageQueues = false;
    options.ManageRoutingProfiles = false;
})
.ImportExistingResources(ImportConfiguration.ConfigureImports);

var host = builder.Build();

// Generate CDK app
var app = new App();
var stack = new ConnectMigrationStack(app, "ExistingConnect-Import");
app.Synth();

Step 4: Test Import (No Changes)

bash
# Synthesize CDK
cdk synth

# Review generated CloudFormation (should show imports only)
cat cdk.out/ExistingConnect-Import.template.json

# Deploy import (no changes to Connect)
cdk deploy ExistingConnect-Import

# ✅ Verify: No resources created/modified
# ✅ Only CDK tracking of existing resources

Phase 2: Add New Flows

Once imports are stable, add new flows that use imported resources.

src/Flows/NewSalesFlow.cs

csharp
using Switchboard;
using Switchboard.Attributes;

[ContactFlow("NewSalesFlow")]
public partial class NewSalesFlow : FlowDefinitionBase
{
    [Action(Order = 1)]
    [Message("Welcome to our new sales experience")]
    public partial void Welcome();

    [Action(Order = 2)]
    [CheckAttribute("Region")]
    [WhenEquals("US-East", Target = "EastQueue")]
    [WhenEquals("US-West", Target = "WestQueue")]
    [Otherwise(Target = "DefaultQueue")]
    public partial void RouteByRegion();

    [Action(Order = 3, Id = "EastQueue")]
    [TransferToQueue("Sales-US-East")] // References imported queue
    public partial void TransferToEast();

    [Action(Order = 4, Id = "WestQueue")]
    [TransferToQueue("Sales-US-West")] // References imported queue
    public partial void TransferToWest();

    [Action(Order = 5, Id = "DefaultQueue")]
    [TransferToQueue("Sales-US-East")]
    public partial void TransferToDefault();
}

Update Program.cs:

csharp
builder.Services.AddSwitchboard(options =>
{
    options.ImportExistingInstance = true;
    options.ExistingInstanceId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

    // Now manage new flows
    options.ManageFlows = true;  // ✅ Changed
    options.ManageQueues = false;
})
.ImportExistingQueues(ImportConfiguration.ConfigureImports)
.AddFlowDefinitions(typeof(Program).Assembly); // ✅ Added

Deploy:

bash
cdk deploy

# ✅ Creates NewSalesFlow
# ✅ Existing flows untouched
# ✅ Queues still managed by console

Phase 3: Gradually Take Over Management

Week 1-2: New Flows Only

csharp
options.ManageFlows = true;
options.ManageQueues = false;
options.ManageRoutingProfiles = false;

Week 3-4: Add Queue Management

Recreate queues as code, gradually switch over:

csharp
[Queue("Sales-US-East")]
public class SalesUSEastQueue : QueueDefinitionBase
{
    [MaxContacts(100)]
    [ServiceLevel(Threshold = 20, Target = 0.80)]
    public override QueueConfiguration Configure()
    {
        // Match existing queue settings exactly
        return new QueueConfiguration
        {
            Description = "Sales queue for US East region",
            HoursOfOperation = "Business-Hours"
        };
    }
}

Migration strategy:

csharp
.ImportExistingQueues(import =>
{
    // Gradually remove queues from import as you recreate in code
    // import.AddQueue("Sales-US-East", "..."); // ❌ Remove when managing via code
    import.AddQueue("Sales-US-West", "...");    // ✅ Still imported
    import.AddQueue("Support-Tier1", "...");    // ✅ Still imported
})
.AddQueueDefinitions(typeof(Program).Assembly); // ✅ Code-managed queues

Month 2-3: Full Management

Eventually manage everything:

csharp
options.ManageFlows = true;
options.ManageQueues = true;
options.ManageRoutingProfiles = true;
options.ManageHoursOfOperation = true;

Rollback Plan

If migration fails, easily rollback:

bash
# 1. Remove Switchboard CDK stacks
cdk destroy --all

# 2. Resources remain in Connect (unaffected)
# 3. Continue using console for management

Best Practices

✅ DO

  • Start with imports only - zero risk
  • Test in dev environment first - validate migration approach
  • Migrate incrementally - one resource type at a time
  • Document existing config - before making changes
  • Keep rollback plan - always have a way back
  • Monitor during migration - watch for unexpected changes

❌ DON'T

  • Manage everything at once - too risky
  • Skip testing phase - always validate in dev first
  • Delete console resources - let framework manage lifecycle
  • Change critical flows - keep them stable during migration
  • Rush the migration - take time to validate each phase

Migration Checklist

Pre-Migration

  • [ ] Inventory all existing resources
  • [ ] Document current configurations
  • [ ] Set up test/dev environment
  • [ ] Train team on Switchboard
  • [ ] Plan rollback strategy

Phase 1: Import (Week 1)

  • [ ] Configure imports for all resources
  • [ ] Deploy CDK with ManageX = false
  • [ ] Verify no changes to Connect
  • [ ] Test referencing imported resources

Phase 2: New Flows (Week 2-3)

  • [ ] Create new flows in code
  • [ ] Reference imported queues/resources
  • [ ] Deploy and test new flows
  • [ ] Monitor for issues

Phase 3: Gradual Takeover (Month 2-3)

  • [ ] Recreate queues as code (one at a time)
  • [ ] Switch ManageQueues = true
  • [ ] Migrate routing profiles
  • [ ] Migrate hours of operation
  • [ ] Remove all imports

Post-Migration

  • [ ] Full Switchboard management
  • [ ] Archive console configurations
  • [ ] Update documentation
  • [ ] Train team on code-first workflow

Troubleshooting

Issue: CDK tries to replace existing resource

Solution:

csharp
// Use import statements in CDK
import.AddQueue("Sales", "arn:...", new ImportOptions
{
    PreserveLogicalId = true, // Prevent replacement
    SkipUpdateCheck = true     // Don't validate against existing
});

Issue: Drift detected between code and console

Solution:

bash
# Detect drift
cdk diff

# Update code to match console
# OR update console to match code (carefully!)

Issue: Cannot reference imported resource

Solution:

csharp
// Ensure resource is imported before use
.ImportExistingQueues(import =>
{
    import.AddQueue("Sales", "arn:...");
})
.AddFlowDefinitions(...); // Can now reference "Sales" queue

Monitoring During Migration

CloudWatch Metrics to Watch

csharp
// Add monitoring stack during migration
new MigrationMonitoringStack(app, "Migration-Monitoring", new MonitoringProps
{
    Alarms = new[]
    {
        new Alarm {
            Metric = "ContactFlowErrors",
            Threshold = 5,
            EvaluationPeriods = 2
        },
        new Alarm {
            Metric = "MissedCalls",
            Threshold = 10,
            EvaluationPeriods = 1
        }
    }
});

Dashboard

  • Contact flow success rate (should stay constant)
  • Queue metrics (wait time, abandoned calls)
  • Lambda invocations (for integrated flows)
  • Resource drift detection

Preview release - Licensing terms TBD before 1.0