Skip to main content

03_service_layer_intro/
03_service_layer_intro.rs

1//! Service Layer Introduction
2//!
3//! This example demonstrates:
4//! - Using the service layer instead of direct store access
5//! - Benefits of the service abstraction
6//! - Thread-safe concurrent access with Arc<RwLock<Store>>
7//!
8//! Scenario: Same operations as example 02, but using the service layer.
9//!
10//! Run with: `cargo run --example 03_service_layer_intro`
11
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use wami::arn::{TenantPath, WamiArn};
15use wami::context::WamiContext;
16use wami::service::{GroupService, RoleService, UserService};
17use wami::store::memory::InMemoryWamiStore;
18use wami::wami::identity::group::requests::CreateGroupRequest;
19use wami::wami::identity::role::requests::CreateRoleRequest;
20use wami::wami::identity::user::requests::{CreateUserRequest, ListUsersRequest};
21
22#[tokio::main]
23async fn main() -> Result<(), Box<dyn std::error::Error>> {
24    println!("=== Service Layer Introduction ===\n");
25
26    // Step 1: Initialize store with Arc<RwLock> for thread-safe access
27    println!("Step 1: Initializing services...\n");
28    let store = Arc::new(RwLock::new(InMemoryWamiStore::default()));
29
30    // Create a WamiContext for operations
31    let context = WamiContext::builder()
32        .instance_id("123456789012")
33        .tenant_path(TenantPath::single(0))
34        .caller_arn(
35            WamiArn::builder()
36                .service(wami::arn::Service::Iam)
37                .tenant_path(TenantPath::single(0))
38                .wami_instance("123456789012")
39                .resource("user", "admin")
40                .build()?,
41        )
42        .is_root(false)
43        .build()?;
44
45    // Create services
46    let user_service = UserService::new(store.clone());
47    let group_service = GroupService::new(store.clone());
48    let role_service = RoleService::new(store.clone());
49
50    println!("✓ Services initialized");
51
52    // === CREATE Operations via Services ===
53    println!("\nStep 2: Creating resources via services...\n");
54
55    // Create users
56    println!("Creating users...");
57    let alice_req = CreateUserRequest {
58        user_name: "alice".to_string(),
59        path: Some("/users/".to_string()),
60        permissions_boundary: None,
61        tags: None,
62    };
63    let alice = user_service.create_user(&context, alice_req).await?;
64    println!("✓ Created user: {}", alice.user_name);
65
66    let bob_req = CreateUserRequest {
67        user_name: "bob".to_string(),
68        path: Some("/users/".to_string()),
69        permissions_boundary: None,
70        tags: None,
71    };
72    user_service.create_user(&context, bob_req).await?;
73    println!("✓ Created user: bob");
74
75    // Create groups
76    println!("\nCreating groups...");
77    let dev_group_req = CreateGroupRequest {
78        group_name: "developers".to_string(),
79        path: Some("/groups/".to_string()),
80        tags: None,
81    };
82    let dev_group = group_service.create_group(&context, dev_group_req).await?;
83    println!("✓ Created group: {}", dev_group.group_name);
84
85    // Create role
86    println!("\nCreating role...");
87    let role_req = CreateRoleRequest {
88        role_name: "deploy-role".to_string(),
89        path: Some("/roles/".to_string()),
90        assume_role_policy_document: r#"{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"sts:AssumeRole"}]}"#.to_string(),
91        description: Some("Role for deployment".to_string()),
92        max_session_duration: Some(3600),
93        permissions_boundary: None,
94        tags: None,
95    };
96    let role = role_service.create_role(&context, role_req).await?;
97    println!("✓ Created role: {}", role.role_name);
98
99    // === READ Operations via Services ===
100    println!("\n\nStep 3: Reading resources via services...\n");
101
102    // Get specific user
103    let alice_retrieved = user_service.get_user(&context, "alice").await?;
104    if let Some(user) = alice_retrieved {
105        println!("✓ Retrieved user 'alice':");
106        println!("  - User ID: {}", user.user_id);
107        println!("  - ARN: {}", user.arn);
108    }
109
110    // List users
111    let users = user_service
112        .list_users(
113            &context,
114            ListUsersRequest {
115                path_prefix: None,
116                pagination: None,
117            },
118        )
119        .await?;
120    println!("\n✓ Found {} users via service:", users.0.len());
121    for user in &users.0 {
122        println!("  - {}", user.user_name);
123    }
124
125    // === UPDATE Operations via Services ===
126    println!("\n\nStep 4: Updating resources via services...\n");
127
128    use wami::wami::identity::user::requests::UpdateUserRequest;
129    let update_req = UpdateUserRequest {
130        user_name: "alice".to_string(),
131        new_user_name: None,
132        new_path: Some("/admin-users/".to_string()),
133    };
134    user_service.update_user(&context, update_req).await?;
135    println!("✓ Updated alice's path to '/admin-users/'");
136
137    // === DELETE Operations via Services ===
138    println!("\n\nStep 5: Deleting resources via services...\n");
139
140    user_service.delete_user(&context, "bob").await?;
141    println!("✓ Deleted user 'bob'");
142
143    // Verify deletion
144    let bob_check = user_service.get_user(&context, "bob").await?;
145    if bob_check.is_none() {
146        println!("  Verified: bob no longer exists");
147    }
148
149    println!("\n✅ Example completed successfully!");
150    println!("Key takeaways:");
151    println!("- Services provide a higher-level API than raw store access");
152    println!("- Arc<RwLock<Store>> enables thread-safe concurrent operations");
153    println!("- Services use request/response DTOs for clean API contracts");
154    println!("- Services encapsulate business logic and validation");
155
156    Ok(())
157}