 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/iOS Dev Challenges

[Challenge] 🛠️ iOS 앱 설계 퓨전 레시피 6부 - 모델링

singularis7 2023. 2. 27. 00:07
반응형

Overview

음식점 서버 API와 연동할 수 있도록 모델 타입을 정의한다.


Restaurant Server API

Order 앱은 다음과 같은 요구사항을 구현해야 한다.

  • 음식점에서 주문 가능한 음식 메뉴 명단을 보여줄 수 있어야 한다.
  • 음식 메뉴의 카테고리를 구분할 수 있어야 한다.
  • 음식 메뉴를 골라서 주문서 명단에 넣을 수 있어야 한다.
  • 주문서 명단을 바탕으로 주문을 넣을 수 있어야 한다.

음식점 서버는 위 요구사항을 다음의 웹서비스 API를 통해 제공하고 있다.

/menu (GET)

음식점에서 주문 가능한 음식 메뉴 명단을 제공하는 api이다.

Request

  • API에 http GET 요청을 한다.
  • requirement - 메뉴 아이템 배열은 JSON 객체의 item키에 담아서 보낸다.
  • optional - URL 쿼리 파라미터로 category 정보를 보내면 해당 카테고리에 속한 음식 메뉴 아이템을 추려서 보내준다.

Response

  • 전체 음식 메뉴 아이템이 담긴 배열을 반환한다.
  • 배열은 JSON 객체의 items 키에 담아서 반환한다.

MenuItem JSON Structure

  • id: 어느 아이템을 다른 것과 구분해 주는 unique Int 값이다.
  • name: 음식 메뉴 아이템의 이름을 지칭하는 string 값이다.
  • description: 음식 메뉴 아이템의 세부 설명을 의미하는 string 값이다.
  • price: 음식 메뉴 아이템의 가격을 의미하는 double 값이다. 미국 달러를 기준으로 한다.
  • category: 아이템을 분류하는데 사용되는 string 값이다.
  • image_url: 음식 메뉴 아이템의 이미지를 불러올 수 있는 url 경로를 지칭한다.

/categories (GET)

음식 메뉴의 카테고리 문자열 정보를 제공하는 api이다.

Request

  • API에 http GET 요청을 한다.

Response

  • 카테고리 문자열이 담긴 배열을 반환한다.
  • 배열은 JSON 객체의 categories 키에 담아서 반환한다.

/images/(file name) (GET)

음식 메뉴의 이미지 데이터를 제공하는 api이다.

Request

  • API에 http GET 요청을 한다.
  • requirement - URL 하위 경로로 이미지의 이름을 보낸다.

Response

  • 음식 메뉴 아이템과 연관된 이미지 데이터를 반환한다.

/order (POST)

주문서에 담긴 음식 메뉴를 통해 주문을 넣어주는 api이다.

Request

  • API에 http POST 요청을 한다.
  • requirement - 메뉴 아이템 id 배열은 JSON 객체의 menuIds 키에 담아서 보낸다.

Response

  • 음식이 준비되기까지 소요되는 예상 시간을 반환한다.
  • 예상 시간은 JSON 객체의 preparation_time 키에 담아서 보낸다.

Abstraction

API의 명세를 보면 복잡해보여서 당황할 수 있다. 하지만 차분히 관찰하며 패턴을 찾고 분류하다 보면 충분히 이해할 수 있다고 믿는다.

API 명세를 이해하기 위해 크게 두 관점으로 분리해 볼 수 있다고 생각한다. 첫번째는 객체지향 설계 관점에서 데이터 구조를 파악하는 것이다. 예를 들어 Entity-Relationship 모델링에서는 데이터를 보유하는 객체는 Entity, 엔티티의 구성 요소는 Attribute, 다른 데이터 보유 객체에 대한 참조는 Relationship이라고 부르는데 이들을 구성해 복잡한 시스템을 모델링하고 있다. (참조 Object Modeling - Apple Developer)

두번째는 네트워크 통신 과정에 필요한 요청 및 응답 형식 모델을 파악하는 것이다. 예를 들어 서버 api는 클라이언트가 요청한 정보를 제공하기 위해 일련의 약속을 정한다. 데이터의 의미 혹은 의도를 추상화하여 입출력 파라미터와 데이터 형식을 규정할 수 있다.

구현 전 참고 사항

애플은 Choosing Between Structures and Classes 아티클 문서를 통해 데이터를 저장하고 행위를 모델링하기 위한 수단을 결정하는데 도움이 되는 가이드라인을 제시하고 있다. Objective-C 상호운용 혹은 데이터의 레퍼런스를 통한 identity 식별이 필요하지 않다면 기본적으로 값타입 struct를 사용할 것을 권장하고 있다.

struct의 Memberwise 생성자를 활용하면 타입의 프로퍼티만 정의하고 별도의 생성자를 개발자가 직접 정의하지 않아도 컴파일러가 자동으로 생성자를 제공해 준다.

데이터 모델

API 명세를 관찰하며 반복해서 보이는 엔티티를 찾아본다. 명사를 유심히 관찰해 보는 것이 도움 될 수 있다. 이번의 경우 음식메뉴아이템과 주문서 가 눈에 띈다. 친절하게도 음식 메뉴 아이템은 속성 정보를 자세하게 설명해주고 있기도 하다.

먼저 음식메뉴아이템 엔티티를 생각해 본다. JSON 객체를 구성하는 속성 모두 Int, Double, String 등의 Primitive 타입이며 다른 엔티티 객체를 참조하고 있지 않다.

이번에는 주문서 엔티티를 생각해본다. 주문을 넣을 메뉴 아이템 명단을 갖고 있어야 하기에 메뉴 아이템의 id 값이 필요하다. 데이터 모델 관점에서 음식메뉴아이템과 관계를 갖고 있음을 생각해 볼 수 있다. 이 점을 생각하며 Swift로 모델 코드를 작성해 보면 다음과 같다.

struct MenuItem { // 음식 메뉴 아이템 엔티티
    var id: Int
    var name: String
    var detailText: String
    var price: Double
    var category: String
    var imageURL: URL
}
struct Order {    // 주문서 엔티티
    var menuItems: [MenuItem] = []
}

Codable 프로토콜

앱을 개발하다 보면 네트워크 연결을 통해 데이터를 보내거나, 디스크에 데이터를 저장하는 기능을 구현하게 된다. 이를 위해선 데이터를 JSON 등의 형식으로 인코딩 및 디코딩하는 작업을 수행해야 한다.

Swift는 표준 라이브러리에서 데이터를 인코딩하거나 디코딩하는 데 사용할 수 있는 표준 접근법을 제공한다. Codable 프로토콜이 핵심적인 역할을 한다.

사용법

사용자 정의 타입에 Codable 프로토콜을 채택하여 외적 표기법으로 인코딩하거나 디코딩할 수 있는 능력을 부여할 수 있다. 자세한 사용법은 Encoding and Decoding Custom Types - Apple Developer을 참조하면 된다. CodingKeys를 활용하는 부분을 주의 깊게 살펴보면 좋다.

동작 원리

Swift에서 프로토콜을 활용하는 메커니즘을 이해하는 것이 필요하다. 궁금하다면 프로토콜 문서가 도움 될 수 있다. 간단한 설명은 다음과 같다.

Codable의 경우 Encodable과 Decodable을 동시에 채택하도록 설계된 타입 별칭으로 정의되었다. 두 프로토콜의 요구사항을 구현해 주면 Codable을 채택한 사용자 정의 타입은 두 프로토콜 타입인 것처럼 동작할 수 있다. 즉, 프로토콜이 선언하는 기능과 프로토콜 기본 구현 기능을 얻을 수 있다는 의미로도 해석할 수 있다.

JSON, PropertyList, XML 등 데이터의 외적 표현으로 사용할 수 있는 다양한 방법이 있다. Foundation 프레임워크의 Archives and Serialization에서 사용자 정의 타입을 다양한 외적 형태로 인코딩하거나 데이터를 객체로 디코딩할 수 있도록 인코더, 디코더 객체를 제공하고 있다.

대표적으로 JSONEncoder나 JSONDecoder 객체가 있다. 이들의 인코딩, 디코딩 함수를 살펴보면 타입 제약이 걸려있는 제네릭 메서드로 정의되어 있다. 여기서 Encodable과 Decodable을 채택하는 타입만 받을 수 있도록 제약이 걸려있다. 즉, 사용자 정의 타입에서 두 프로토콜을 채택함으로써 해당 프로토콜처럼 행동할 때 갖고 있는 메서드나 프로퍼티를 활용해 인코딩, 디코딩 작업을 수행하고 있는 것이다.

Codable 한 Entity

앞서 정의한 Entity를 Codable 하게 만들어보자! 이때 JSON에 사용될 키값은 API 정의를 따른다.

extension MenuItem: Codable {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case detailText = "description"
        case price
        case category
        case imageURL = "image_url"
    }
}
extension Order: Codable {}

네트워크 모델

앞서 살펴본 API 명세를 통해 서버로부터 데이터 응답을 받을 때 사용되는 형식을 관찰할 수 있었다. 이를 바탕으로 코드로 구현하면 다음과 같다.

MenuResponse

struct MenuResponse: Codable {
    let items: [MenuItem]
}

CategoriesResponse

struct CategoriesResponse: Codable {
    let categories: [String]   
}

OrderResponse

struct OrderResponse {
    let preperationTime: Int
}

extension OrderResponse: Codable {
    enum CodingKeys: String, CodingKey {
        case preperationTime = "preparation_time"
    }
}

마무리

Codable 프로토콜을 활용하여 음식점 서버 API와 연동할 수 있도록 모델 타입을 정의해 보았다. 다음에는 프로토콜 지향 프로그래밍을 활용해 네트워크 코드를 모델링해볼 것이다. 🤗

반응형