Enterprise SOAP Integration with Lather
Mix.install([
{:lather, path: ".."}, # For local development
# {:lather, "~> 1.0"}, # Use this for hex package
{:finch, "~> 0.18"},
{:kino, "~> 0.12"},
{:jason, "~> 1.4"}
])
Introduction
This Livebook demonstrates how to integrate with enterprise SOAP services using the Lather library. Weβll cover:
- Authentication strategies (Basic Auth, WS-Security)
- SSL/TLS configuration for secure connections
- Complex parameter structures and data mapping
- Error handling and retry strategies
- Performance optimization techniques
Environment Setup
# Start applications
{:ok, _} = Application.ensure_all_started(:lather)
# Configure Finch with enterprise-grade connection pooling
children = [
{Finch,
name: Lather.Finch,
pools: %{
# High-traffic enterprise endpoints
"https://enterprise.example.com" => [
size: 25,
count: 4,
protocols: [:http2, :http1]
],
# Default pool for other endpoints
:default => [size: 10, count: 2]
}
}
]
{:ok, _supervisor} = Supervisor.start_link(children, strategy: :one_for_one)
IO.puts("π’ Enterprise Lather environment ready!")
Configuration Panel
Letβs create an interactive panel to configure our enterprise SOAP connection:
# Enterprise service configuration inputs
wsdl_input = Kino.Input.text("WSDL URL", default: "https://enterprise.example.com/services/UserService?wsdl")
username_input = Kino.Input.text("Username", default: "demo_user")
password_input = Kino.Input.password("Password")
timeout_input = Kino.Input.number("Timeout (ms)", default: 60000)
ssl_verify_input = Kino.Input.checkbox("Verify SSL", default: true)
# Layout the configuration
config_form = Kino.Layout.grid([
[wsdl_input],
[username_input, password_input],
[timeout_input, ssl_verify_input]
], columns: 2)
config_form
# Build configuration from inputs
enterprise_config = %{
wsdl_url: Kino.Input.read(wsdl_input),
username: Kino.Input.read(username_input),
password: Kino.Input.read(password_input),
timeout: Kino.Input.read(timeout_input),
ssl_verify: Kino.Input.read(ssl_verify_input)
}
IO.puts("π§ Enterprise Configuration:")
IO.inspect(Map.drop(enterprise_config, [:password]), pretty: true)
IO.puts("Password: #{"*" |> String.duplicate(String.length(enterprise_config.password))}")
SSL/TLS Configuration
For enterprise services, proper SSL configuration is crucial:
defmodule EnterpriseSSL do
def ssl_options(verify \\ true) do
base_options = [
versions: [:"tlsv1.2", :"tlsv1.3"],
ciphers: :ssl.cipher_suites(:default, :"tlsv1.2")
]
if verify do
[
verify: :verify_peer,
cacerts: :public_key.cacerts_get(),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
] ++ base_options
else
[
verify: :verify_none
] ++ base_options
end
end
def client_options(config) do
[
basic_auth: {config.username, config.password},
timeout: config.timeout,
pool_timeout: 10_000,
ssl_options: ssl_options(config.ssl_verify),
headers: [
{"User-Agent", "LatherDemo/1.0"},
{"Accept", "text/xml"},
{"X-API-Version", "2.0"}
]
]
end
end
# Display the SSL configuration
ssl_config = EnterpriseSSL.ssl_options(enterprise_config.ssl_verify)
IO.puts("π SSL Configuration:")
IO.inspect(ssl_config, pretty: true)
Enterprise Client Connection
Now letβs attempt to connect to an enterprise SOAP service:
# Create enterprise client with full configuration
enterprise_client_result =
case Lather.DynamicClient.new(
enterprise_config.wsdl_url,
EnterpriseSSL.client_options(enterprise_config)
) do
{:ok, client} ->
IO.puts("β
Successfully connected to enterprise service!")
# Store for use in other cells
Process.put(:enterprise_client, client)
# Show service capabilities
operations = Lather.DynamicClient.list_operations(client)
IO.puts("\nπ Available Operations (#{length(operations)}):")
Enum.each(operations, fn op -> IO.puts(" β’ #{op}") end)
{:ok, client}
{:error, error} ->
IO.puts("β Failed to connect to enterprise service:")
IO.puts(" Type: #{error.type}")
IO.puts(" Details: #{Lather.Error.format_error(error)}")
# Check if the error is recoverable
if Lather.Error.recoverable?(error) do
IO.puts("\nπ This error might be recoverable - you could retry the connection")
end
{:error, error}
end
enterprise_client_result
Mock Enterprise Service Demo
Since we may not have access to a real enterprise service, letβs create a mock demonstration of enterprise SOAP operations:
defmodule MockEnterpriseDemo do
def simulate_user_search do
# Simulate complex enterprise search parameters
%{
"searchRequest" => %{
"criteria" => %{
"filters" => [
%{
"field" => "department",
"operator" => "equals",
"value" => "Engineering"
},
%{
"field" => "active",
"operator" => "equals",
"value" => true
},
%{
"field" => "hireDate",
"operator" => "greaterThan",
"value" => "2023-01-01"
}
],
"sorting" => [
%{
"field" => "lastName",
"direction" => "asc"
},
%{
"field" => "firstName",
"direction" => "asc"
}
],
"pagination" => %{
"pageSize" => 25,
"pageNumber" => 1
}
},
"includeFields" => [
"personalInfo",
"workInfo",
"permissions",
"projects"
],
"options" => %{
"includeMetadata" => true,
"auditTrail" => true,
"securityContext" => %{
"requesterId" => "api_user_123",
"reason" => "System Integration",
"ipAddress" => "10.0.1.100"
}
}
}
}
end
def simulate_user_creation do
%{
"createUserRequest" => %{
"user" => %{
"personalInfo" => %{
"firstName" => "John",
"lastName" => "Doe",
"email" => "john.doe@company.com",
"phone" => "+1-555-0123",
"title" => "Mr.",
"dateOfBirth" => "1985-03-15"
},
"workInfo" => %{
"employeeId" => "EMP2024001",
"department" => "Engineering",
"title" => "Senior Software Engineer",
"manager" => "jane.smith@company.com",
"startDate" => "2024-02-01",
"location" => %{
"office" => "New York HQ",
"floor" => "15",
"desk" => "15-A-042"
},
"costCenter" => "CC-ENG-001"
},
"permissions" => [
"access_development_tools",
"read_project_data",
"write_code_repositories",
"access_staging_environment"
],
"roles" => [
"Developer",
"CodeReviewer"
],
"metadata" => %{
"source" => "HR_SYSTEM",
"requestId" => generate_request_id(),
"createdBy" => "hr_admin",
"businessJustification" => "New hire onboarding"
}
},
"options" => %{
"sendWelcomeEmail" => true,
"provisionAccounts" => true,
"assignDefaultPermissions" => true,
"notifyManager" => true
}
}
}
end
def simulate_batch_operation do
%{
"batchRequest" => %{
"operations" => [
%{
"type" => "update",
"userId" => "EMP001",
"changes" => %{
"workInfo" => %{
"title" => "Principal Software Engineer",
"department" => "Platform Engineering"
}
}
},
%{
"type" => "update",
"userId" => "EMP002",
"changes" => %{
"permissions" => [
"access_development_tools",
"read_project_data",
"write_code_repositories",
"access_production_logs"
]
}
},
%{
"type" => "create",
"user" => %{
"personalInfo" => %{
"firstName" => "Alice",
"lastName" => "Johnson",
"email" => "alice.johnson@company.com"
},
"workInfo" => %{
"department" => "Marketing",
"title" => "Marketing Specialist"
}
}
}
],
"options" => %{
"continueOnError" => true,
"transactional" => false,
"auditLevel" => "detailed"
}
}
}
end
defp generate_request_id do
:crypto.strong_rand_bytes(16)
|> Base.encode16(case: :lower)
end
end
# Display example enterprise parameters
IO.puts("π₯ Example: User Search Parameters")
IO.inspect(MockEnterpriseDemo.simulate_user_search(), pretty: true, limit: :infinity)
Parameter Validation Demo
Letβs demonstrate how Lather validates complex parameters:
# Create sample operation info for validation demo
sample_operation_info = %{
name: "SearchUsers",
input_parts: [
%{
name: "searchRequest",
type: "SearchRequest",
required: true
}
],
output_parts: [
%{
name: "searchResponse",
type: "SearchResponse"
}
]
}
# Test parameter validation
search_params = MockEnterpriseDemo.simulate_user_search()
IO.puts("π Parameter Validation Demo")
IO.puts("=" |> String.duplicate(40))
# Simulate validation (in real usage, this happens automatically)
case Lather.Operation.Builder.validate_parameters(sample_operation_info, search_params) do
:ok ->
IO.puts("β
Parameters passed validation")
# Show parameter structure analysis
IO.puts("\nπ Parameter Analysis:")
IO.puts(" β’ Root elements: #{map_size(search_params)}")
search_request = search_params["searchRequest"]
if search_request do
criteria = search_request["criteria"]
if criteria do
filters = criteria["filters"] || []
IO.puts(" β’ Search filters: #{length(filters)}")
IO.puts(" β’ Sorting rules: #{length(criteria["sorting"] || [])}")
IO.puts(" β’ Include fields: #{length(search_request["includeFields"] || [])}")
end
end
{:error, error} ->
IO.puts("β Parameter validation failed:")
IO.puts(" #{Lather.Error.format_error(error)}")
end
Error Handling Strategies
Enterprise applications need robust error handling. Letβs explore different error scenarios:
defmodule EnterpriseErrorHandling do
def handle_soap_error(error) do
case error do
%{type: :soap_fault, fault_code: "Client", fault_string: message} ->
{
:client_error,
"Client Error: #{message}",
"Check your request parameters and try again"
}
%{type: :soap_fault, fault_code: "Server", fault_string: message} ->
{
:server_error,
"Server Error: #{message}",
"The service encountered an error. Retry may help"
}
%{type: :http_error, status: 401} ->
{
:authentication_error,
"Authentication failed",
"Check your credentials and authentication method"
}
%{type: :http_error, status: 403} ->
{
:authorization_error,
"Access denied",
"Your account may not have permission for this operation"
}
%{type: :http_error, status: 500} ->
{
:server_error,
"Internal server error",
"The service is experiencing issues. Try again later"
}
%{type: :transport_error, reason: :timeout} ->
{
:timeout_error,
"Request timed out",
"The service didn't respond in time. Consider increasing timeout or retry"
}
%{type: :transport_error, reason: :nxdomain} ->
{
:connection_error,
"Service not found",
"Check the service URL and network connectivity"
}
_ ->
{
:unknown_error,
"Unknown error occurred",
"Check logs for more details"
}
end
end
def retry_strategy(error, attempt) do
max_retries = 3
base_delay = 1000 # 1 second
cond do
attempt >= max_retries ->
{:stop, "Maximum retries exceeded"}
Lather.Error.recoverable?(error) ->
delay = base_delay * :math.pow(2, attempt) # Exponential backoff
{:retry, round(delay)}
true ->
{:stop, "Error is not recoverable"}
end
end
def execute_with_retry(client, operation, params, attempt \\ 0) do
case Lather.DynamicClient.call(client, operation, params) do
{:ok, response} ->
if attempt > 0 do
IO.puts("β
Operation succeeded after #{attempt} retries")
end
{:ok, response}
{:error, error} ->
{error_type, message, suggestion} = handle_soap_error(error)
IO.puts("β #{error_type}: #{message}")
IO.puts("π‘ Suggestion: #{suggestion}")
case retry_strategy(error, attempt) do
{:retry, delay} ->
IO.puts("π Retrying in #{delay}ms (attempt #{attempt + 1})...")
Process.sleep(delay)
execute_with_retry(client, operation, params, attempt + 1)
{:stop, reason} ->
IO.puts("π Stopping retries: #{reason}")
{:error, error}
end
end
end
end
# Demonstrate error categorization
sample_errors = [
%{type: :soap_fault, fault_code: "Client", fault_string: "Invalid parameter"},
%{type: :http_error, status: 401},
%{type: :transport_error, reason: :timeout},
%{type: :transport_error, reason: :nxdomain}
]
IO.puts("π¨ Error Handling Examples:")
Enum.each(sample_errors, fn error ->
{error_type, message, suggestion} = EnterpriseErrorHandling.handle_soap_error(error)
IO.puts("\n#{error_type}:")
IO.puts(" Message: #{message}")
IO.puts(" Suggestion: #{suggestion}")
end)
WS-Security Authentication Demo
For services requiring WS-Security, hereβs how to set it up:
defmodule WSSecurityDemo do
def create_username_token(username, password) do
# Create WS-Security username token
Lather.Auth.WSSecurity.username_token(
username,
password,
timestamp: true,
nonce: true
)
end
def create_security_header(username, password) do
username_token = create_username_token(username, password)
Lather.Auth.WSSecurity.security_header(username_token, [])
end
def wssecurity_client_options(username, password, base_options) do
security_header = create_security_header(username, password)
base_options ++ [
soap_headers: [security_header]
]
end
end
# Example WS-Security configuration
wssecurity_config = WSSecurityDemo.wssecurity_client_options(
enterprise_config.username,
enterprise_config.password,
EnterpriseSSL.client_options(enterprise_config)
)
IO.puts("π WS-Security Configuration Example:")
IO.puts(" β Username token with timestamp")
IO.puts(" β Nonce for replay protection")
IO.puts(" β Password digest (when supported)")
IO.puts(" β Security header in SOAP envelope")
# Show the security header structure (without sensitive data)
sample_header = WSSecurityDemo.create_security_header("demo_user", "hidden")
IO.puts("\nπ Security Header Structure:")
IO.inspect(sample_header, pretty: true)
Performance Monitoring
Letβs create a performance monitoring dashboard for SOAP operations:
defmodule PerformanceMonitor do
def track_operation(operation_name, fun) do
start_time = System.monotonic_time(:millisecond)
result = fun.()
end_time = System.monotonic_time(:millisecond)
duration = end_time - start_time
status = case result do
{:ok, _} -> :success
{:error, _} -> :error
end
%{
operation: operation_name,
duration_ms: duration,
status: status,
timestamp: DateTime.utc_now()
}
end
def simulate_performance_data do
# Simulate various operation performance metrics
operations = ["SearchUsers", "CreateUser", "UpdateUser", "DeleteUser", "GetUserDetails"]
Enum.map(1..20, fn _ ->
operation = Enum.random(operations)
duration = Enum.random(100..5000) # 100ms to 5s
status = if :rand.uniform() > 0.1, do: :success, else: :error
%{
operation: operation,
duration_ms: duration,
status: status,
timestamp: DateTime.utc_now()
}
end)
end
def analyze_performance(metrics) do
total_operations = length(metrics)
successful_ops = Enum.count(metrics, fn m -> m.status == :success end)
error_rate = (total_operations - successful_ops) / total_operations * 100
successful_metrics = Enum.filter(metrics, fn m -> m.status == :success end)
durations = Enum.map(successful_metrics, fn m -> m.duration_ms end)
avg_duration = if length(durations) > 0 do
Enum.sum(durations) / length(durations)
else
0
end
max_duration = if length(durations) > 0, do: Enum.max(durations), else: 0
min_duration = if length(durations) > 0, do: Enum.min(durations), else: 0
%{
total_operations: total_operations,
success_rate: 100 - error_rate,
error_rate: error_rate,
avg_duration_ms: round(avg_duration),
max_duration_ms: max_duration,
min_duration_ms: min_duration
}
end
end
# Generate and analyze performance data
performance_data = PerformanceMonitor.simulate_performance_data()
analysis = PerformanceMonitor.analyze_performance(performance_data)
IO.puts("π SOAP Operations Performance Analysis")
IO.puts("=" |> String.duplicate(45))
IO.puts("Total Operations: #{analysis.total_operations}")
IO.puts("Success Rate: #{Float.round(analysis.success_rate, 1)}%")
IO.puts("Error Rate: #{Float.round(analysis.error_rate, 1)}%")
IO.puts("Average Duration: #{analysis.avg_duration_ms}ms")
IO.puts("Fastest Operation: #{analysis.min_duration_ms}ms")
IO.puts("Slowest Operation: #{analysis.max_duration_ms}ms")
# Show recent operations
IO.puts("\nπ Recent Operations:")
performance_data
|> Enum.take(5)
|> Enum.each(fn metric ->
status_icon = if metric.status == :success, do: "β
", else: "β"
IO.puts(" #{status_icon} #{metric.operation}: #{metric.duration_ms}ms")
end)
Enterprise Integration Checklist
Letβs create an interactive checklist for enterprise SOAP integration:
# Enterprise integration checklist
checklist_items = [
"π WSDL Analysis Complete",
"π Authentication Configured",
"π SSL/TLS Security Verified",
"β‘ Connection Pooling Optimized",
"π‘οΈ Error Handling Implemented",
"π Retry Strategy Defined",
"π Performance Monitoring Setup",
"π§ͺ Integration Testing Complete",
"π Documentation Updated",
"π Production Deployment Ready"
]
# Create checkboxes for each item
checklist_widgets = Enum.map(checklist_items, fn item ->
Kino.Input.checkbox(item, default: false)
end)
# Layout the checklist
checklist_form = Kino.Layout.grid(
Enum.map(checklist_widgets, fn widget -> [widget] end),
columns: 1
)
IO.puts("β
Enterprise SOAP Integration Checklist")
checklist_form
# Check completion status
completed_items = Enum.count(checklist_widgets, fn widget ->
Kino.Input.read(widget)
end)
total_items = length(checklist_items)
completion_percentage = completed_items / total_items * 100
IO.puts("π Integration Progress: #{completed_items}/#{total_items} (#{Float.round(completion_percentage, 1)}%)")
if completion_percentage == 100.0 do
IO.puts("π Congratulations! Your enterprise SOAP integration is ready for production!")
elsif completion_percentage >= 80.0 do
IO.puts("π Almost there! Just a few more items to complete.")
elsif completion_percentage >= 50.0 do
IO.puts("β‘ Good progress! You're halfway to production readiness.")
else
IO.puts("ποΈ Keep going! There's still work to be done for a production-ready integration.")
end
Best Practices Summary
Here are the key best practices for enterprise SOAP integration with Lather:
best_practices = """
π’ ENTERPRISE SOAP INTEGRATION BEST PRACTICES
π§ Configuration:
β’ Use environment variables for credentials
β’ Configure appropriate timeouts (60s+ for enterprise)
β’ Enable SSL verification in production
β’ Set up connection pooling for high traffic
π‘οΈ Security:
β’ Always use HTTPS in production
β’ Implement proper certificate validation
β’ Use WS-Security for enhanced authentication
β’ Log security events but not sensitive data
π¨ Error Handling:
β’ Distinguish between client and server errors
β’ Implement exponential backoff for retries
β’ Set maximum retry limits
β’ Log errors with correlation IDs
β‘ Performance:
β’ Reuse client instances across requests
β’ Use connection pooling effectively
β’ Monitor response times and error rates
β’ Implement circuit breakers for failing services
π§ͺ Testing:
β’ Test with mock services first
β’ Validate complex parameter structures
β’ Test error scenarios and recovery
β’ Load test with realistic traffic patterns
π Monitoring:
β’ Track operation success rates
β’ Monitor response times and timeouts
β’ Set up alerting for service degradation
β’ Use correlation IDs for request tracing
π Deployment:
β’ Use feature flags for gradual rollouts
β’ Keep WSDL versions synchronized
β’ Plan for service versioning
β’ Document API changes and dependencies
"""
IO.puts(best_practices)
Next Steps
Youβve now seen how to build robust enterprise SOAP integrations with Lather! Hereβs what to explore next:
- Custom Authentication: Implement custom authentication schemes for your specific enterprise requirements
- Service Mesh Integration: Integrate with service mesh technologies for enhanced observability
- Advanced Error Recovery: Build sophisticated error recovery and circuit breaker patterns
- Performance Optimization: Fine-tune connection pools and caching strategies for your workload
Happy enterprise SOAP integration! π’β¨