---
# System prepended metadata

title: '은행 창구 매니저 [STEP 3] 토털이, Aaron'

---

@junbangg
안녕하세요!
반갑습니다 알라딘:) 이번 스탭에는 동시성 프로그래밍에 대해서 고민을 많이 했습니다..!
리뷰 잘 부탁드리겠습니다! 💪🏽

# 은행 창구 매니저 [STEP 3] 토털이, Aaron

## 개발한 내용
- 예금과 대출 업무 큐 분리
- 대출과 예금 큐 각각 동시적으로 work 메서드 처리 구현
- 확장성과 다형성을 고려하여 workable 프로토콜 및 예금, 대출 은행원 구조체 구현
- 은행원 타입 두가지(DepositClerk, LoanClerk)로 분리, Workable protocol 생성
- serial Queue를 활용하여 Race Condition을 방지해 줌.
- Workable 프로토콜 내부의 scheduleWork() 함수를 정의하여 DispatchWorkItem을 생성해주도록 함.
- Bank init()에서 은행원 수와, 예금인원수를 입력하면 대출 및 예금 은행원이 자동 생성되도록 해줌.

## 고민한 점
- 같은 업무를 처리하는 은행원 사이에서 같은 고객 대기열에 접근할 때 발생하는 race condtion 해결
- 은행원 타입의 다형성을 위해 프로토콜 분리 및 구조체 구현

### 1. 은행원의 역할

이전 코드에서는 은행원의 역할이 무엇이던 상관없이 은행원은 print만을 담당하였습니다. 그 코드는 아래와 같습니다. 

```swift
struct BankClerk {

    func work(for customer: Customer) {
        print("\(customer.number)번 고객 업무 시작")
    }
}
```

하지만, 객체지향적인 설계가 아니라는 생각이 들었고, 은행원을 enum 타입으로 대출 및 예금 형태로 구분해주는 것이 아닌 Workable 프로토콜을 따르는 대출 은행원, 예금 은행원으로 만들어주는 것이 더 낫다고 판단하였습니다.

```swift
protocol Workable {
    var service: Service { get }
    var processingTime: Double { get }
    func work(for customer: Customer)
    func scheduleWork(from customerQueue: Queue<Customer>) -> DispatchWorkItem
    static var serviceQueue: DispatchQueue { get }
}
```
위와 같은 형태로 프로토콜을 생성해 은행원이 일을 스케쥴링 하여 일을 할 수 있도록 하고 각각의 타입 프로퍼티로 serviceQueue를 설정하여 race condition을 방지할 수 있는 serial queue를 만들어 주었습니다.

### 2. 같은 큐 접근에 대한 race condtion 방지

- `DispatchQueue.gloabl().async`를 사용해 동시적으로 처리할 경우 같은 고객 대기열을 접근하게 되어 race condition이 발생할 수 있습니다. <br>
    이를 방지하기 위해서 `Dispatch Gruop`을 사용해 해당 그룹의 task가 끝나기 전까지 동기적으로 고객을 받아 race condition을 방지했습니다.
    
    ```swift
    func serve() {
            let group = DispatchGroup()

            clerks.forEach { clerk in
                switch clerk.service {
                case .loan:
                    DispatchQueue.global().async(group: group, execute: clerk.scheduleWork(from: loanQueue))
                case .deposit:
                    DispatchQueue.global().async(group: group, execute: clerk.scheduleWork(from: depositQueue))
                }
            }

            group.wait()
        }
    ```
    ```swift
    func scheduleWork(from customerQueue: Queue<Customer>) -> DispatchWorkItem {
            let depositWorkItem = DispatchWorkItem {
                while customerQueue.isEmpty == false {
                    var customer: Customer?
                    Self.serviceQueue.sync {
                        customer = customerQueue.dequeue()
                    }
                    guard let customer = customer else {
                        return
                    }
                    self.work(for: customer)
                }
            }
            return depositWorkItem
        }
    ```

### 3. randomElement()
- customer를 customerQueue에 할당 할 때 임의로 `serviceType`을 어떻게 할지 고민했습니다.
    서로 고민한 결과 `Service` 열겨형에 `CaseIterable`프로토콜을 채택해 배열처럼 다루며 `randomElement()`를 사용해 구현했습니다.
    ```swif
    enum Service: CaseIterable {
        case deposit
        case loan

        var message: String {
            switch self {
            case .deposit:
                return "예금"
            case .loan:
                return "대출"
            }
        }
    }
    ```

### 4. manageCustomer에서 고객 큐 분리
- 처음 `Service` enum 타입을 고민할 때 미리 고객 대기열을 
    - 업무별로 분리하여 각각의 큐를 만들어 할당해줄지
    - 전체 큐를 Clerk이 할당받아 자기 업무에 맞는 고객을 찾아갈지
고민해 주었습니다. 저희는 결국 업무별로 분리하여 각각의 큐를 만들어 할당해주기로 하고 코드를 작성해 주었습니다.


## 조언을 구하고 싶은 부분

- async가 제대로 작동하는지 확인할 방법이 없어 정확히 잘 작동하는지 감을 잡기가 어려웠습니다. 저희가 알 수 있는 방법이 있을까요? 테스트 코드를 작성하면 될까요?🙏
- race condition을 해결하기 위해 serial 큐를 작성해 주었는데, 아래 코드에서 
  ```swift
    func scheduleWork(from customerQueue: Queue<Customer>) -> DispatchWorkItem {
            let depositWorkItem = DispatchWorkItem {
                while customerQueue.isEmpty == false {
                    var customer: Customer?
                    Self.serviceQueue.sync {
                        customer = customerQueue.dequeue()
                    }
                    guard let customer = customer else {
                        return
                    }
                    self.work(for: customer)
                }
            }
            return depositWorkItem
        }
    ```
`while customerQueue.isEmpty == false`의 경우도 customerQueue에 대한 접근인데 race condition이 일어나지 않을까 걱정이 되었습니다. 이 경우에도 race condition을 막아줄 수 있는 방법 이 있을까요?