Section 5/171 menit

5. Best Practice: Desain Actor

5. Best Practice: Desain Actor

BP 5.1: Buat Method Actor Sependek Mungkin

Method actor yang panjang = lock yang dipegang lama = lebih banyak contention. Hop in, do the minimum, hop out.

swift
// ❌ Method actor yang melakukan terlalu banyak
actor OrderStore {
    var orders: [Order] = []

    func placeOrder(_ order: Order) async throws {
        let validated = try await validate(order)  // I/O lama
        let priced = try await price(validated)    // I/O lama
        let processed = try await charge(priced)   // I/O lama
        orders.append(processed)
    }
}

// ✓ Side effect I/O di luar actor, mutasi state di akhir
struct OrderService {
    let store: OrderStore
    func placeOrder(_ order: Order) async throws {
        let validated = try await validate(order)
        let priced = try await price(validated)
        let processed = try await charge(priced)
        await store.append(processed)  // hanya hop saat dibutuhkan
    }
}

actor OrderStore {
    private(set) var orders: [Order] = []
    func append(_ order: Order) { orders.append(order) }
}

BP 5.2: Hindari Method Actor yang Mengembalikan Reference Mutable

Actor melindungi state internal. Tapi kalau kamu mengembalikan reference ke object mutable, isolasi pecah.

swift
// ❌ Bocor reference mutable
actor UserStore {
    private var users: [UUID: NSMutableArray] = [:]
    func purchases(for id: UUID) -> NSMutableArray {
        return users[id] ?? NSMutableArray()  // caller bisa mutate di luar actor
    }
}

// ✓ Return value type / snapshot
actor UserStore {
    private var users: [UUID: [Purchase]] = [:]
    func purchases(for id: UUID) -> [Purchase] {
        return users[id] ?? []  // value type, copy on assign
    }
}

BP 5.3: Pisahkan "Producer Actor" dan "Consumer Actor"

Kalau satu actor menerima event dan juga di-query, sering lebih baik dipisah.

swift
// ✓ Pisah responsibility
actor EventIngester {
    private let store: EventStore
    init(store: EventStore) { self.store = store }
    func ingest(_ e: Event) async { await store.append(e) }
}

actor EventStore {
    private var events: [Event] = []
    func append(_ e: Event) { events.append(e) }
    func recent(limit: Int) -> [Event] { Array(events.suffix(limit)) }
}

BP 5.4: Gunakan nonisolated untuk Method yang Tidak Akses State

Method yang hanya menggunakan parameter / let properties tidak butuh hop. Tandai nonisolated.

swift
actor Calculator {
    private var history: [Double] = []  // mutable, butuh isolation
    let precision: Int = 4  // immutable

    func compute(_ x: Double, _ y: Double) -> Double {
        let result = (x + y).rounded(to: precision)
        history.append(result)
        return result
    }

    // Tidak akses history, tidak butuh hop
    nonisolated func formatPrecision() -> String {
        return "\(precision) digits"
    }
}

BP 5.5: Batch Operasi Saat Mungkin

Banyak await ke method actor untuk operasi kecil = banyak hop. Sediakan API batch.

swift
// ❌ Hop per item
actor Logger {
    var entries: [String] = []
    func log(_ s: String) { entries.append(s) }
}

for msg in messages {
    await logger.log(msg)  // N hop
}

// ✓ Satu hop
extension Logger {
    func logAll(_ items: [String]) {
        entries.append(contentsOf: items)
    }
}

await logger.logAll(messages)