Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CountryCodeAPIService 응답 처리 로직 개선 #30

Closed
yeahg-dev opened this issue Feb 24, 2023 · 0 comments
Closed

CountryCodeAPIService 응답 처리 로직 개선 #30

yeahg-dev opened this issue Feb 24, 2023 · 0 comments
Assignees
Labels
Refactoring 가독성 향상, 유연한 구조를 위한 리팩터링

Comments

@yeahg-dev
Copy link
Owner

yeahg-dev commented Feb 24, 2023

🚨 문제 설명

  • CountryCodeAPIService에서 apikey오류 response도 status code: 200, Content-Type: xml 타입으로 응답
  • 따라서 기존 APIService의 request를 요청하는 메서드에서 해당 오류를 걸러낼 수 없었고, 오류 처리를 할 수 없었음
extension APIService {
    
    func execute<T: APIRequest>(request: T) async throws -> T.APIResponse {
        guard let urlRequest = request.urlRequest else {
            throw APIError.invalidURL
        }
        
        let (data, response) = try await session.data(for: urlRequest)
        
        guard let response = response as? HTTPURLResponse,
              // 원하는 json데이터가 아닌 xml데이터가 200으로 도착
              (200...299).contains(response.statusCode) else {
            print("\(T.self) failed to receive success response(status code: 200-299)")
            throw APIError.HTTPResponseFailure
        }
        
        guard let parsedData: T.APIResponse = parse(response: data) else {
            print("parsing failed. type: \(T.APIResponse.Type.self) ")
            throw APIError.invalidParsedData
        }
        
        return parsedData
    }
}

✅ 해결 방법

문제 분석

  • response의 header의 Content-Type 필드 값을 조회하면 오류 체크 및 처리가 가능

해결 방법

  • Content-Type이 application/json;charset=UTF-8이 아니면 에러를 반환
struct CountryCodeAPIService: APIService {
    
    var session: URLSession = URLSession(configuration: .default)
    
    func requestAllCountryCode() async throws -> [CountryCode] {
        let request = CountryCodeListRequest(pageNo: 1, numOfRows: 240)
        let countryCodeList: CountryCodeList = try await getResponse(request: request)
        return countryCodeList.data
    }
    
    func getResponse<T: APIRequest>(request: T) async throws -> T.APIResponse {
        guard let urlRequest = request.urlRequest else {
            throw APIError.invalidURL
        }
        
        let (data, response) = try await session.data(for: urlRequest)
        
        guard let response = response as? HTTPURLResponse,
              (200...299).contains(response.statusCode) else {
            print("\(T.self) failed to receive success response(status code: 200-299)")
            throw APIError.HTTPResponseFailure
        }
        
        guard let contentType = response.value(forHTTPHeaderField: "Content-Type"),
        contentType == "application/json;charset=UTF-8" else {
            print("\(T.self) : response Content-Type is \(String(describing: response.value(forHTTPHeaderField: "Content-Type")))")
            throw APIError.HTTPResponseFailure
        }
        
        guard let parsedData: T.APIResponse = parse(response: data) else {
            print("parsing failed. type: \(T.APIResponse.Type.self) ")
            throw APIError.invalidParsedData
        }
        
        return parsedData
    }
    
}
  • 기존 APIService의 getResponse메서드는 프로토콜 기본 구현으로 다른 Service에서도 사용되므로, 단일책임원칙과 개방폐쇄원칙을 위반한 코드임
  • 따라서 getResponse를 프로토콜에 정의, 추상화하여 확장에 용이하도록 리팩터링
  • Combine 프레임워크 또한 불필요하게 APIService에 종속되어있으므로, CombineAPIService 프로토콜을 정의하여 역할을 분리
protocol APIService {
    
    var session: URLSession { get set }
    
    func getResponse<T: APIRequest>(request: T) async throws -> T.APIResponse
    
}

protocol CombineAPIService: APIService {
    
    func getResponse<T: APIRequest>(
        request: T)
    -> AnyPublisher<T.APIResponse, Error>
    
}

👩🏻‍💻결과와 피드백

  • APIService의 확장성 향상됨
  • 불필요한 Combine을 import를 제거함으로써 최적화함
@yeahg-dev yeahg-dev added the Refactoring 가독성 향상, 유연한 구조를 위한 리팩터링 label Feb 24, 2023
@yeahg-dev yeahg-dev self-assigned this Feb 24, 2023
yeahg-dev added a commit that referenced this issue Mar 17, 2023
- 기존 APIService의 execute, getResponse 메서드는 OCP, SRP를 위반하므로 프로토콜로 인터페이스를 제공하기로 수정, 각 구현객체의 변경과 확장에 용이하도록 함
- execute를 getResponse로 리네임하여 동일한 역할을 하는 메서드임을 명시
- response의 header field의 Content-Type확인하는 로직 추가, json이 아닐시 에러 반환하도록 오류 처리
yeahg-dev added a commit that referenced this issue Mar 17, 2023
- APIService에서 Combine 프레임워크와의 의존성을 분리하기 위해 APIService를 상속한 CombineAPIService 프로토콜 정의
- Combine을 사용하는 API에서만 해당 프로토콜을 채택하도록 구현
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Refactoring 가독성 향상, 유연한 구조를 위한 리팩터링
Projects
Status: Done
Development

No branches or pull requests

1 participant